Поддавшись общей истерии на хабре,
(а именно «Предупреждаю Недвижимость Изменился» переводит Гуглекс всеми любимый «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 запостил
Полностью поддерживаю, но… НЕТ.
Ну что же, напильник и кувалда.
Я за чистый код! Логирование, перехваты исключений, проверка прав доступа, всевозможные уведомления и т.д. — задымляют код.Очистить код позволят древние знания шаблона Proxy, например habrahabr.ru/post/88722.
Осложню себе жизнь и буду использовать классы а не интерфейсы.
Уведомления, ничего личного
И сам заместитель, взявший на себя всю грязную работу
Создам консольное приложение, где как не в консоли проверять ПредупреждаюНедвижимостьИзменился
Плюсы: чистейший класс Data, все просто и понятно, можно создать кучу разных прокси logger, access и тд, скомбинировать, безопасно удалять и добавлять разный функционал не относящийся к непосредственной работе приложения.
Минусы: Бесполезно, это подмена понятий вьюмодель я практически превратил в модель, получив еще 1 звено шаблона Model -> ViewModel -> Proxy -> View, одно из назначений ViewModel — уведомление, конечно в vm можно оставить подготовку данных… Плюс ко всему кода стало еще больше, хотя вроде как ответственность vm снизилась, ох solid SOLID.
Истерия! Пришло время АОП, про аоп написано довольно много и на теории я останавливаться не буду.
я работаю с IUnityContainer, т.к. его можно считать коробочным, хорошо взаимодействует с Prism.
А вот и универсальное поведение уведомлятора, жуть
ну и кусок кода, где все это связывается
Result ----->
плюшка для особо ленивых
минимизация регистрации
Нельзя оставлять иллюзий, в основе все тот же ЗАМЕСТИТЕЛЬ, только мы о нем не знаем ТсссССсссссс.
Плюсы: теперь можно сделать VM из всего у чего есть виртуальные свойства, полная минимизация кода, чистый класс без ПредупреждаюНедвижимостьИзменился, всяких атрибутов и тому подобных техномагий.
Минусы: совершенно не просто, нужно разбираться в контейнере и его возможностях аоп, не явность в чистом виде, воспринимаемая как магия, куча рефликсии — это не для слабонервных, просев по производительности.
Проведя небольшой эксперимент на сотрудниках, результат плачевный, программисты испытывали боль когда я попросил разобраться как это работает.
Но оно работает.
В целом внедрять это не стали и чуть не придали праведному огню, аоп это круто, но все зависит от уровня команды и их стремлению к заморочкам.
P.S. Всем кого пугает эта жесть рекомендую OnPropertyChanged(nameof(Property)) — оптимально по всем показателям, кроме того что это нужно прописывать РУКАМИ. Бу!
(а именно «Предупреждаю Недвижимость Изменился» переводит Гуглекс всеми любимый «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)) — оптимально по всем показателям, кроме того что это нужно прописывать РУКАМИ. Бу!
Комментарии (7)
vdasus
11.04.2016 13:12Я в простых случаях пользуюсь https://www.nuget.org/packages/KindOfMagic/ и забыл об этой проблеме (чуть подробнее — https://kindofmagic.codeplex.com/)
Veikedo
11.04.2016 15:37В своё время, делали так:
public static Boolean Set(this INotifyPropertyChangedEx o, ref T property, T value, [CallerMemberName] String propertyName = null)
{
if (Equals(property, value))
{
return false;
}
property = value;
o.NotifyOfPropertyChange(propertyName);
return true;
}
использовали:
public bool IsLoading
{
get { return _isLoading; }
set { this.Set(ref _isLoading, value); }
}
Вкупе со сниппетом довольно удобно. Плохой момент в том, что INotifyPropertyChangedEx из пакета Caliburn.Micro (но несложнен в реализации)
astudent
1) Прекращайте истереть и используйте Fody/PropertyChanged. Минус — это AOP (например, не поставить брэкпоинт, хотя это нужно крайне редко). Плюс — автоматические вычислимые свойства.
2) Для борьбы с чистотой кода можно вместо каждого нотифицируемого свойства использовать Prism/ObservableObject или runceel/ReactiveProperty. Биндиться придется к *.Value, c поддержкой вычислимых свойств тоже придется повозится, но зато на каждое свойство уникальное событие — не надо работать со строками.
3) Мы в одном проекте использовали шаблоны Visual Studio T4. Можно написать шаблон, который по основному классу будет генерировать во время написания кода partial-составляющую с нужными уведомлениями и прочим. Очень гибкое решение, можно много чего так реализовать. Из минусов — привязка к конкретной студии разработки.
InWake
Истерики нет, меня вполне устраивает класические OnPropertyChanged через expression,
при написании IInterceptionBehavior это не проблема.У ObservableObject интересная наследственность System.Windows.FrameworkElement<-Microsoft.Practices.Prism.ObservableObject + странный биндинг, не уверен что это чистый код. Автоген это конечно круто, а чем вынос дыма в partial лучше наследования или композиции?