Сахарные инжекции в C#

C# — продуманный и развитый язык программирования, в котором предусмотрено немало синтаксического сахара, упрощающего написание рутинного кода. Но всё-таки существует ряд сценариев, где нужно проявить некоторую смекалку и изобретательность, чтобы сохранить стройность и красоту.В статье мы рассмотрим некоторые такие случаи, как широкоизвестные, так и не очень.b24129b82a9e42fba40fddb01381c2dc.pngДекларация и вызов событий

Иногда на собеседовании могут задать вопрос, как правильно вызывать события? Классический способ декларации события выглядит следующим образом:

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() . obj.Of().Property1.Of.Property2 = 77; Реализация его очень простая: public static class Sugar { public static T Of(this object o) { return (T) o; }

public static bool Is(this object o) { return o is T; }

public static T As(this object o) where T: class { return o as T; } } ForEachУ класса

List есть удобный метод ForEach, однако его полезно расширить и для коллекций других типов public static void ForEach(this IEnumerable collection, Action action) { foreach (var item in collection) { action (item); } } Теперь некоторые операции можно описать лишь одной строкой, не прибегая к методу ToList (). persons.Where (p => p.HasChanged).ForEach (p => p.Save ()); Sync AwaitАсинхронное программирование с async/await — огромный шаг вперёд, но в редких случаях, например, для обратной совместимости нужно асинхронные методы превращать в синхронные. Тут поможет небольшой класс-адаптер.

public static class AsyncAdapter { public static TResult Await(this Task operation) { var result = default (TResult); Task.Factory.StartNew (async () => result = await operation).Wait (); return result; }

public static TResult Await(this IAsyncOperation operation) { return operation.AsTask ().Result; }

public static TResult Await(this IAsyncOperationWithProgress operation) { return operation.AsTask ().Result; } } Применение его очень простое: // var result = await source.GetItemsAsync (); var result = source.GetItemsAsync ().Await (); Командыxaml-ориентированные разработчики хорошо знакомы с паттерном ICommand. В рамках MVVM-подхода встречаются разные его реализации. Но чтобы грамотно реализовать паттерн, необходимо учитывать тот факт, что визуальный контрол обычно подписывается на событие CanExecuteChanged у команды, что может вести к утечкам памяти при использовании динамических интерфейсов. Всё это часто ведёт к усложнению синтаксиса работы с командами.

Интерес представляет концепция контекстно-ориентировынных команд.

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); }; } }