Сахарные инжекции в C#
C# — продуманный и развитый язык программирования, в котором предусмотрено немало синтаксического сахара, упрощающего написание рутинного кода. Но всё-таки существует ряд сценариев, где нужно проявить некоторую смекалку и изобретательность, чтобы сохранить стройность и красоту.В статье мы рассмотрим некоторые такие случаи, как широкоизвестные, так и не очень.Декларация и вызов событий
Иногда на собеседовании могут задать вопрос, как правильно вызывать события? Классический способ декларации события выглядит следующим образом:
public event EventHandler HandlerName; , а сам вызов так: var handler = HandlerName; if (handler!= null) handler (o, e); Мы копируем обработчик в отдельную переменную handler, поскольку это защищает от NullReferenceException в многопоточных приложениях. Ведь при записи if (HandlerName!= null) HandlerName (o, e); другой поток может отписаться от события уже после проверки на null, что приведёт к исключению, если это был единственный или последний подписчик.Но существует более лаконичный путь без дополнительных проверок:
public event EventHandler HandlerName = (o, e) => { }; public event EventHandler HandlerName = delegate { }; HandlerName (o, e); Отписаться от пустого делегата нельзя, поэтому HandlerName гарантировано не равно null.Справедливости ради стоит заметить, что в многопоточных приложениях оба этих способа могут привести к вызову обработчика события у уже отписавшегося объекта, поэтому на стороне подписчика нужно предусматривать такое поведение. Кроме того, искусственно возможно даже смоделировать ситуацию вызова обработчика у объекта уже утилизированного сборщиком мусора. Конечно, это приведёт к исключению.
Паттерны INotifyPropertyChanging и INotifyPropertyChanged
Весьма часто для уведомления об изменении значений свойств прибегают к следующей записи:
private string _name;
public string Name { get { return _name; } set { _name = value; var handler = PropertyChanged; if (handler!= null) PropertyChanged (this, new PropertyChangedEventArgs («Name»)); } } Её вряд ли можно назвать лаконичной да и строковая константа с именем свойства выглядит не очень красиво. Поэтому был разработан более элегантный способ нотификации на основе лямбда-выражений. public string Name { get { return Get (() => Name); } set { Set (() => Name, value); } } Иногда можно услышать возражения, что этот способ медленный. Да, в синтетических тестах он уступает первому, но в реальных приложениях никакого сколько-нибудь заметного снижения производительности не происходит, ведь это большая редкость, когда свойство изменяется с огромной частотой и нужно отслеживать каждое такое изменение. Кроме того, допустимо комбинирование различных способов уведомления, поэтому вариант с лямбда-выражениями очень даже хорош на практике.Привычная подписка на уведомления об изменении события выглядит так:
PropertyChanged += (o, e) => { if (e.PropertyName!= «Name») return; // do something } Однако существует и другой вариант достойный внимания с перегрузкой индексатора: this[() => Name].PropertyChanging += (o, e) => { // do somethig }; this[() => Name].PropertyChanged += (o, e) => { // do somethig }; viewModel[() => viewModel.Name].PropertyChanged += (o, e) => { // do somethig }; Если нужно выполнить немного действий, то легко можно уложиться в одну строку кода: this[() => Name].PropertyChanged += (o, e) => SaveChanges (); C помощью этого же подхода удобно реализуется валидация свойств в комбинации с паттерном IDataErrorInfo. this[() => Name].Validation += () => Error = Name == null || Name.Length < 3 ? Unity.App.Localize("InvalidName") : null; О производительности беспокоится в данном случае также не стоит, поскольку разбор лямбда-выражения выполняется только один раз во время самой подписки.Приведение типа методом Of
Встречаются порой ситуации, когда нужно выполнить несколько преобразований типа подряд:
((Type2)((Type1)obj).Property1)).Property2 = 77;
Количество скобок зашкаливает и читаемость падает. На выручку приходит дженерик-метод-расширение
Of
public static bool Is
public static T As
List
public static class AsyncAdapter
{
public static TResult Await
public static TResult Await
public static TResult Await
Интерес представляет концепция контекстно-ориентировынных команд.
public class HelloViewModel: ContextObject, IExposable { public string Message { get { return Get (() => Message); } set { Set (() => Message, value); } }
public virtual void Expose () { this[() => Message].PropertyChanged += (sender, args) => Context.Make.RaiseCanExecuteChanged (); this[Context.Make].CanExecute += (sender, args) => args.CanExecute = ! string.IsNullOrEmpty (Message);
this[Context.Make].Executed += async (sender, args) =>
{
await MessageService.ShowAsync (Message);
};
}
}