Поддавшись общей истерии на хабре,

(а именно «Предупреждаю Недвижимость Изменился» переводит Гуглекс всеми любимый «I Notify Property Changed») по поводу уведомлений об изменении. Я решил посмотреть на сколько далеко продвинулось человечество в изобретении велосипедов.
OnPropertyChanged(«Property»)
OnPropertyChanged(()=>Property)
SetProperty(ref storage, value)
[CallerMemberName]
[Magic]
АОП разных мастей
даже предлагают roslyn.codeplex.com/discussions/550266 почему бы м нет.
Круче всех все же nameof(Property) — осторожно си шарп 6.
Последствия истерии выражаются в следующих работах
habrahabr.ru/post/199378
habrahabr.ru/post/246469
habrahabr.ru/post/95211
Лично меня устраивает варианты OnPropertyChanged(nameof(Property)) и OnPropertyChanged(()=>Property), первый вариант работает быстрее.
Но чаще всего я использую SetProperty(ref storage, value), коробочный вариант BindableBase.
Хабражитель Scrooge2 запостил
Хватит изобреать велосипеды.
Использовать обычный INotifyPropertyChanged руки не отпадут, без всяких но.
Полностью поддерживаю, но… НЕТ.
Ну что же, напильник и кувалда.
Я за чистый код! Логирование, перехваты исключений, проверка прав доступа, всевозможные уведомления и т.д. — задымляют код.Очистить код позволят древние знания шаблона Proxy, например habrahabr.ru/post/88722.
Осложню себе жизнь и буду использовать классы а не интерфейсы.
public class Data { public virtual int Value { get; set; } public virtual string Source { get; set; } }
Уведомления, ничего личного
class BindableObject : BindableBase { public new bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { return base.SetProperty<T>(ref storage, value, propertyName); } }
И сам заместитель, взявший на себя всю грязную работу
public class ProxyData : Data, INotifyPropertyChanged { Data _target; BindableObject _notifier; public ProxyData(Data target) { _target = target; _notifier = new BindableObject(); } public override int Value { set { int newValue = 0; if (_notifier.SetProperty(ref newValue, value)) base.Value = newValue; } } public override string Source { set { string newSource = null; if (_notifier.SetProperty(ref newSource, value)) base.Source = newSource; } } public event PropertyChangedEventHandler PropertyChanged { add { _notifier.PropertyChanged += value; } remove { _notifier.PropertyChanged -= value; } } }
Создам консольное приложение, где как не в консоли проверять ПредупреждаюНедвижимостьИзменился
data = new ProxyData(new Data()); (data as INotifyPropertyChanged).PropertyChanged += (s, e) => { Console.WriteLine(string.Format("Property {0} changed!", e.PropertyName)); }; data.Value = 10; data.Source = "List";
Плюсы: чистейший класс Data, все просто и понятно, можно создать кучу разных прокси logger, access и тд, скомбинировать, безопасно удалять и добавлять разный функционал не относящийся к непосредственной работе приложения.
Минусы: Бесполезно, это подмена понятий вьюмодель я практически превратил в модель, получив еще 1 звено шаблона Model -> ViewModel -> Proxy -> View, одно из назначений ViewModel — уведомление, конечно в vm можно оставить подготовку данных… Плюс ко всему кода стало еще больше, хотя вроде как ответственность vm снизилась, ох solid SOLID.
Истерия! Пришло время АОП, про аоп написано довольно много и на теории я останавливаться не буду.
я работаю с IUnityContainer, т.к. его можно считать коробочным, хорошо взаимодействует с Prism.
А вот и универсальное поведение уведомлятора, жуть
class NotifyPropertyChangedBehavior : IInterceptionBehavior, INotifyPropertyChanged { static readonly MethodBase _add; static readonly MethodBase _remove; static NotifyPropertyChangedBehavior() { var methods = typeof(INotifyPropertyChanged).GetMethods(); _add = methods[0]; _remove = methods[1]; } public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { IMethodReturn result = null; if (IsPropertyChanged(input)) if (SubscribeUnsubscribe(input)) result = input.CreateMethodReturn(null); else { PropertyInfo property; if (IsSetMethodCalled(out property, input)) result = SetValue(property, input, getNext); } return result ?? getNext()(input, getNext); } public bool WillExecute { get { return true; } } public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Проверка вызова мутатора /// </summary> /// <returns></returns> bool IsSetMethodCalled(out PropertyInfo property, IMethodInvocation input) { string propertyName = input.MethodBase.Name.TrimStart("set_".ToArray()); property = input.Target.GetType().GetProperty(propertyName); return property != null; } /// <summary> /// Установить /// </summary> /// <returns></returns> IMethodReturn SetValue(PropertyInfo property, IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { var oldValue = property.GetValue(input.Target, new object[0]); var newValue = input.Arguments[0]; IMethodReturn result = null; //обновление только если пришло действительно новое значение if (!Equals(oldValue, newValue)) { result = getNext()(input, getNext); if (PropertyChanged != null) PropertyChanged(input.Target, new PropertyChangedEventArgs(property.Name)); } else result = input.CreateMethodReturn(null); return result; } /// <summary> /// Проверка вызова методов INotifyPropertyChanged /// </summary> bool SubscribeUnsubscribe(IMethodInvocation input) { if (input.MethodBase == _add) { PropertyChanged += (PropertyChangedEventHandler)input.Arguments[0]; return true; } else if (input.MethodBase == _remove) { PropertyChanged -= (PropertyChangedEventHandler)input.Arguments[0]; return true; } return false; } /// <summary> /// Вызов на экземпляре реализующим INotifyPropertyChanged /// </summary> bool IsPropertyChanged(IMethodInvocation input) { return input.Target is INotifyPropertyChanged; } }
ну и кусок кода, где все это связывается
IUnityContainer container = new UnityContainer(); container.AddNewExtension<Interception>(); container.RegisterType<Data>(new Interceptor<VirtualMethodInterceptor>(), new InterceptionBehavior<NotifyPropertyChangedBehavior>() , new AdditionalInterface<INotifyPropertyChanged>()); var data = container.Resolve<Data>(); (data as INotifyPropertyChanged).PropertyChanged += (s, e) => { Console.WriteLine(string.Format("Property {0} changed!", e.PropertyName)); }; data.Value = 10; data.Source = "List"; data.Value = 10; Console.ReadKey();
Result ——>

плюшка для особо ленивых
public static class Extensions { public static IUnityContainer RegisterViewModel<T>(this IUnityContainer container) where T : class { container.AddNewExtension<Interception>(); return container.RegisterType<T>(new Interceptor<VirtualMethodInterceptor>(), new InterceptionBehavior<NotifyPropertyChangedBehavior>() , new AdditionalInterface<INotifyPropertyChanged>()); } }
минимизация регистрации
container.RegisterViewModel<Data>();
Нельзя оставлять иллюзий, в основе все тот же ЗАМЕСТИТЕЛЬ, только мы о нем не знаем ТсссССсссссс.
Плюсы: теперь можно сделать VM из всего у чего есть виртуальные свойства, полная минимизация кода, чистый класс без ПредупреждаюНедвижимостьИзменился, всяких атрибутов и тому подобных техномагий.
Минусы: совершенно не просто, нужно разбираться в контейнере и его возможностях аоп, не явность в чистом виде, воспринимаемая как магия, куча рефликсии — это не для слабонервных, просев по производительности.
Проведя небольшой эксперимент на сотрудниках, результат плачевный, программисты испытывали боль когда я попросил разобраться как это работает.
Но оно работает.
В целом внедрять это не стали и чуть не придали праведному огню, аоп это круто, но все зависит от уровня команды и их стремлению к заморочкам.
P.S. Всем кого пугает эта жесть рекомендую OnPropertyChanged(nameof(Property)) — оптимально по всем показателям, кроме того что это нужно прописывать РУКАМИ. Бу!
ссылка на оригинал статьи https://habrahabr.ru/post/281294/
Добавить комментарий