День семьсот одиннадцатый. #MoreEffectiveCSharp
19. Избегайте Перегрузки Методов, Определённых в Базовых Классах
Когда базовый класс выбирает имя члена, он назначает определённый семантический смысл этому имени. Ни при каких обстоятельствах производный класс не должен использовать то же самое имя для других целей. И всё же есть много причин, по которым производный класс может захотеть использовать то же имя. Например, реализовать ту же семантику другим способом или с другими параметрами. Но вы не должны перегружать методы, объявленные в базовом классе.
Правила разрешения перегрузки сложны по определению. Возможными кандидатами могут быть методы класса, любого из его базовых классов, методы расширения класса или интерфейсов, которые он реализует. Добавьте к этому обобщённые методы и обобщённые методы расширения, методы с необязательными параметрами, и уже вообще непонятно, сможет ли хоть кто-нибудь поручиться, какой из методов будет вызван. Вы действительно хотите ещё больше усложнить ситуацию?
Добавление перегрузки для метода базового класса увеличивает вероятность того, что ваша интерпретация спецификации не совпадёт с интерпретацией компилятора, и, безусловно, запутает ваших пользователей. Решение простое: выберите другое имя метода.
Рассмотрим пару примеров:
Больше ада! Добавим дженериков:
Да, вы можете удивить друзей на вечеринке программистов глубокими познаниями логики разрешения перегрузок в C#. Но не ожидайте, что пользователи вашего API будут иметь такие подробные знания, чтобы правильно использовать ваш API. Просто не перегружайте методы, объявленные в базовом классе. Это не представляет никакой ценности и только приведёт ваших пользователей в замешательство.
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 19.
19. Избегайте Перегрузки Методов, Определённых в Базовых Классах
Когда базовый класс выбирает имя члена, он назначает определённый семантический смысл этому имени. Ни при каких обстоятельствах производный класс не должен использовать то же самое имя для других целей. И всё же есть много причин, по которым производный класс может захотеть использовать то же имя. Например, реализовать ту же семантику другим способом или с другими параметрами. Но вы не должны перегружать методы, объявленные в базовом классе.
Правила разрешения перегрузки сложны по определению. Возможными кандидатами могут быть методы класса, любого из его базовых классов, методы расширения класса или интерфейсов, которые он реализует. Добавьте к этому обобщённые методы и обобщённые методы расширения, методы с необязательными параметрами, и уже вообще непонятно, сможет ли хоть кто-нибудь поручиться, какой из методов будет вызван. Вы действительно хотите ещё больше усложнить ситуацию?
Добавление перегрузки для метода базового класса увеличивает вероятность того, что ваша интерпретация спецификации не совпадёт с интерпретацией компилятора, и, безусловно, запутает ваших пользователей. Решение простое: выберите другое имя метода.
Рассмотрим пару примеров:
public class Fruit { }
public class Apple : Fruit { }
Вот класс с методом, использующим производный параметр (Apple):public class Animal {
public void Eat(Apple food) =>
WriteLine("Animal.Eat");
}
var obj1 = new Animal();
obj1.Eat(new Apple());
Понятно, что это выведет "Animal.Eat". Добавим производный класс с перегруженным методом с параметром базового типа:public class Monkey : Animal {
public void Eat(Fruit food) =>
WriteLine("Monkey.Eat");
}
Итак, что выведет следующий код?var obj2 = new Monkey();Оба вызова выведут "Monkey.Eat". Всегда в первую очередь вызывается метод производного класса, даже если в базовом классе есть более подходящий кандидат. Смысл в том, что автор производного класса лучше знает сценарий использования, поэтому производному методу отдаётся предпочтение. А если вот так:
obj2.Eat(new Apple());
obj2.Eat(new Fruit());
Animal obj3 = new Monkey();Смотрим внимательно: тип времени компиляции
obj3.Eat(new Apple());
obj3 - Animal (базовый), хотя, во время выполнения тип будет Monkey (производный). Метод Eat не виртуальный, поэтому obj3.Eat() должен использовать Animal.Eat.Больше ада! Добавим дженериков:
public class Animal {
…
public void Consume(IEnumerable<Apple> food) =>
WriteLine("Animal.Consume");
}
И перегрузку с коллекцией базового типа в производном классе:public class Monkey : Animal {
…
public void Consume(IEnumerable<Fruit> food) =>
WriteLine("Monkey.Consume");
}
var food = new List<Apple> { new Apple(), new Apple() };
var obj2 = new Monkey();
obj2.Consume(food);
Что будет выведено на этот раз? Начиная с C#4.0 обобщённые интерфейсы поддерживают ковариантность и контравариантность. Это означает, что Monkey.Consume является кандидатом для IEnumerable<Apple>, хотя формально тип его параметра IEnumerable<Fruit>. Однако более ранние версии C# не поддерживают вариантности, и в них обобщённые параметры инвариантны. В этом случае единственным кандидатом будет Animal.Consume.Да, вы можете удивить друзей на вечеринке программистов глубокими познаниями логики разрешения перегрузок в C#. Но не ожидайте, что пользователи вашего API будут иметь такие подробные знания, чтобы правильно использовать ваш API. Просто не перегружайте методы, объявленные в базовом классе. Это не представляет никакой ценности и только приведёт ваших пользователей в замешательство.
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 19.
День семьсот двадцать четвёртый. #MoreEffectiveCSharp
20. Учитывайте, Что События Усиливают Связанность Объектов во Время Выполнения
Кажется, что события предоставляют способ полностью отделить ваш класс от классов, которые он должен уведомлять. Вы определяете тип события и позволяете подписываться на него. Внутри вашего класса вы вызываете событие. Ваш класс ничего не знает о подписчиках и не налагает ограничений на них. Код можно расширить, создавая любое необходимое поведение при возникновении этих событий. Однако не всё так просто.
Начнём с того, что некоторые типы аргументов событий содержат флаги состояния, которые предписывают вашему классу выполнять определённые операции.
Во время выполнения возникает ещё одна форма связи между источником события и подписчиками. Источник содержит ссылку на делегат, который предоставляет подписчик. Время жизни объекта подписчика теперь будет соответствовать времени жизни объекта источника. Источник будет вызывать обработчик подписчика всякий раз, когда происходит событие. Но это не должно продолжаться после удаления подписчика. То есть подписчикам на события необходимо реализовать паттерн Disposable и отписываться от события в методе Dispose(). В противном случае подписчики продолжат существовать, поскольку в источнике будут ссылки на их делегаты.
Это еще один случай, когда связывание во время выполнения может вам дорого обойтись несмотря на то, что связь кажется более слабой. Связь на основе событий ослабляет статическую связь между типами, но за счет более тесной связи между источником события и подписчиками во время выполнения.
Вы должны учесть эти проблемы в своем дизайне, чтобы использовать события.
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 20.
20. Учитывайте, Что События Усиливают Связанность Объектов во Время Выполнения
Кажется, что события предоставляют способ полностью отделить ваш класс от классов, которые он должен уведомлять. Вы определяете тип события и позволяете подписываться на него. Внутри вашего класса вы вызываете событие. Ваш класс ничего не знает о подписчиках и не налагает ограничений на них. Код можно расширить, создавая любое необходимое поведение при возникновении этих событий. Однако не всё так просто.
Начнём с того, что некоторые типы аргументов событий содержат флаги состояния, которые предписывают вашему классу выполнять определённые операции.
public class WorkerEngine {
public event
EventHandler<WorkerEventArgs> OnProgress;
public void DoLotsOfStuff() {
for (int i = 0; i < 100; i++) {
SomeWork();
var args = new WorkerEventArgs();
args.Percent = i;
OnProgress?.Invoke(this, args);
if (args.Cancel) return;
}
}
private void SomeWork(){…}
}
Теперь все подписчики на это событие связаны. Если один подписчик запросит отмену операции, установив Cancel в true, другой может отменить это. Таким образом, последний подписчик в цепочке может переопределить действие любого предыдущего. Невозможно заставить иметь только одного подписчика, и нет способа гарантировать, что делегат какого-то из подписчиков будет выполнен последним. Вы можете изменить аргументы события, чтобы гарантировать, что после установки флага отмены ни один подписчик не сможет его выключить:public class WorkerEventArgs : EventArgs {
public int Percent { get; set; }
public bool Cancel { get; private set; }
public void RequestCancel() {
Cancel = true;
}
}
Это изменение сработает в этом случае, но так можно сделать не всегда. Если вам нужно убедиться, что есть ровно один подписчик, придётся выбрать другой способ связи классов. Например, определить интерфейс и вызывать метод интерфейса вместо события. Или запрашивать делегат подписчика в качестве параметра метода. Затем этот единственный подписчик может решить, хочет ли он поддерживать несколько подписчиков и как организовать семантику запросов на отмену.Во время выполнения возникает ещё одна форма связи между источником события и подписчиками. Источник содержит ссылку на делегат, который предоставляет подписчик. Время жизни объекта подписчика теперь будет соответствовать времени жизни объекта источника. Источник будет вызывать обработчик подписчика всякий раз, когда происходит событие. Но это не должно продолжаться после удаления подписчика. То есть подписчикам на события необходимо реализовать паттерн Disposable и отписываться от события в методе Dispose(). В противном случае подписчики продолжат существовать, поскольку в источнике будут ссылки на их делегаты.
Это еще один случай, когда связывание во время выполнения может вам дорого обойтись несмотря на то, что связь кажется более слабой. Связь на основе событий ослабляет статическую связь между типами, но за счет более тесной связи между источником события и подписчиками во время выполнения.
Вы должны учесть эти проблемы в своем дизайне, чтобы использовать события.
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 20.
День 1191.
Подборка тегов, используемых в постах на канале, чтобы облегчить поиск. Не могу гарантировать, что все 1190 постов идеально и корректно помечены тегами, но всё-таки, эта подборка должна помочь.
Общие
Эти посты на совершенно разные темы, помечены этими тегами только с целью различать общую направленность поста.
#ЗаметкиНаПолях – технические посты. Краткие описания теории, особенности языка C# и платформы .NET, примеры кода, и т.п.
#Шпаргалка - примеры кода, команды для утилит и т.п.
#Юмор – шутки, комиксы и просто весёлые тексты или ссылки на видео.
#Оффтоп – всё прочее.
Специализированные
Эти теги более тематические, выделяют основную тему поста.
#Карьера – советы по повышению продуктивности, карьерному росту, прохождению собеседований и т.п.
#Книги – обзоры книг, которые (чаще всего) я лично прочитал, либо ещё нет, но советую прочитать.
#Курсы – обзоры и ссылки на онлайн курсы.
#МоиИнструменты – различные программы, утилиты и расширения IDE, которые я использую в работе.
#ЧтоНовенького – новости из мира .NET.
Узкоспециализированные
Эти теги относятся к определённой узкой теме.
#AsyncTips – серия постов из книги Стивена Клири “Конкурентность в C#”
#AsyncAwaitFAQ – серия постов “Самые Частые Ошибки при Работе с async/await.”
#BestPractices – советы по лучшим практикам, паттернам разработки.
#DesignPatterns – всё о паттернах проектирования, SOLID, IDEALS и т.п.
#DotNetAZ – серия постов с описанием терминов из мира .NET.
#GC – серия постов “Топ Вопросов о Памяти в .NET.” от Конрада Кокосы.
#MoreEffectiveCSharp – серия постов из книги Билла Вагнера “More Effective C#”.
#Testing – всё о тестировании кода.
#TipsAndTricks – советы и трюки, в основном по функционалу Visual Studio.
#Quiz - опросы в виде викторины.
#97Вещей – серия постов из книги “97 Вещей, Которые Должен Знать Каждый Программист”.
#ВопросыНаСобеседовании – тег говорит сам за себя, самые часто задаваемые вопросы на собеседовании по C#, ASP.NET и .NET.
#ЗадачиНаСобеседовании – похоже на вопросы, но здесь больше приводятся практические задачи. Чаще всего это 2 поста: собственно задача и ответ с разбором.
#КакСтатьСеньором – серия постов «Как Стать Сеньором» с советами о продвижении по карьерной лестнице.
Помимо этого, можно просто воспользоваться поиском по постам и попробовать найти то, что вам нужно.
Подборка тегов, используемых в постах на канале, чтобы облегчить поиск. Не могу гарантировать, что все 1190 постов идеально и корректно помечены тегами, но всё-таки, эта подборка должна помочь.
Общие
Эти посты на совершенно разные темы, помечены этими тегами только с целью различать общую направленность поста.
#ЗаметкиНаПолях – технические посты. Краткие описания теории, особенности языка C# и платформы .NET, примеры кода, и т.п.
#Шпаргалка - примеры кода, команды для утилит и т.п.
#Юмор – шутки, комиксы и просто весёлые тексты или ссылки на видео.
#Оффтоп – всё прочее.
Специализированные
Эти теги более тематические, выделяют основную тему поста.
#Карьера – советы по повышению продуктивности, карьерному росту, прохождению собеседований и т.п.
#Книги – обзоры книг, которые (чаще всего) я лично прочитал, либо ещё нет, но советую прочитать.
#Курсы – обзоры и ссылки на онлайн курсы.
#МоиИнструменты – различные программы, утилиты и расширения IDE, которые я использую в работе.
#ЧтоНовенького – новости из мира .NET.
Узкоспециализированные
Эти теги относятся к определённой узкой теме.
#AsyncTips – серия постов из книги Стивена Клири “Конкурентность в C#”
#AsyncAwaitFAQ – серия постов “Самые Частые Ошибки при Работе с async/await.”
#BestPractices – советы по лучшим практикам, паттернам разработки.
#DesignPatterns – всё о паттернах проектирования, SOLID, IDEALS и т.п.
#DotNetAZ – серия постов с описанием терминов из мира .NET.
#GC – серия постов “Топ Вопросов о Памяти в .NET.” от Конрада Кокосы.
#MoreEffectiveCSharp – серия постов из книги Билла Вагнера “More Effective C#”.
#Testing – всё о тестировании кода.
#TipsAndTricks – советы и трюки, в основном по функционалу Visual Studio.
#Quiz - опросы в виде викторины.
#97Вещей – серия постов из книги “97 Вещей, Которые Должен Знать Каждый Программист”.
#ВопросыНаСобеседовании – тег говорит сам за себя, самые часто задаваемые вопросы на собеседовании по C#, ASP.NET и .NET.
#ЗадачиНаСобеседовании – похоже на вопросы, но здесь больше приводятся практические задачи. Чаще всего это 2 поста: собственно задача и ответ с разбором.
#КакСтатьСеньором – серия постов «Как Стать Сеньором» с советами о продвижении по карьерной лестнице.
Помимо этого, можно просто воспользоваться поиском по постам и попробовать найти то, что вам нужно.
1👍60👎1