The implementation in the model (in the MVVM pattern) of each public variable is as follows:

private bool _isGraphSet; public bool isGraphSet { get { return _isGraphSet; } private set { _isGraphSet = value; OnPropertyChanged("isGraphSet"); } } 

Not at all happy. Are there ways to make things more human?

2 answers 2

I would suggest using such a blank.

First, you define the base class for your VMs:

 class VM : INotifyPropertyChanged { protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; RaisePropertyChanged(propertyName); return true; } protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); public event PropertyChangedEventHandler PropertyChanged; } 

Then, in your VM classes, you write this:

 class GraphVM : VM { private bool _isGraphSet; public bool isGraphSet { get { return _isGraphSet; } private set { Set(ref _isGraphSet, value); } } } 

For IL-weavers like Fody, I have an ambiguous attitude.

On the one hand, AOP is as if good and correct.

On the other hand, there are bugs with Fody. Worse, since code generation happens behind the scenes, it is unlikely that you can easily debug related problems if something went wrong: you don’t have the source code!

Therefore, I would wait until replace / original ( here and here ) is implemented in C # to have full control over the code generation with the possibility of step-by-step debugging.

  • one
    It all depends on the style of development. Not everyone likes to throw exceptions in setters, but for dependent fields there are special attributes AlsoNotifyFor / DependsOn - with this approach, there were no problems with Fody, but there is also DoNotCheckEquality. The only inconvenience of Fody is that the vrappers over the models still have to be manually written. - vitidev
  • @vitidev: A sample of the problem is on the link. The fact that you did not stumble upon a bug - thank God, but the other participant was not so lucky. - VladD
  • one
    But there is no bug. Fody simply writes a check for equality of value at the beginning of the method. 123 -> value assigned. 123q- threw an exception and _BottomString = value; it did not work and the value remained 123. We remove the backspace q and the new value coincides with the old one and exits the setter before executing the user code (where you can put a breakpoint). The DoNotCheckEquality setting does not fit this check and the setter always works - vitidev
  • one
    You did not understand. the setter is always called. I described the problem in detail by your link. This is just a implementation feature that Fody cram its code at the beginning and end. This bug is a moot point. I would say that you need to ask the author of the fody to add an attribute for cases with throw Exception - vitidev
  • one
    Fody doesn't require anything. It just generates a boilerplate code. Similarly, in the case of exceptions, the prog will not be able to use your Set <T> (ref T field, but fully implement the raw method. There is no difference. - vitidev

Once I wrote such a bike using Castle DynamicProxy , which intercepts the call to the property setter in the view models and automatically tugs PropertyChanged :

 // Базовый класс для всех VM public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; internal virtual void RaisePropertyChanged(string propertyName) { var evt = PropertyChanged; if (evt != null) evt(this, new PropertyChangedEventArgs(propertyName)); } // Создание прокси к VM с помощью Castle.DynamicProxy public static T Create<T>() where T : ViewModelBase { var generator = new ProxyGenerator(); return generator.CreateClassProxy<T>(new PropertyChangedInterceptor()); } } // Тестовая VM. Ручного кода, как видите, совсем нет public class TestViewModel : ViewModelBase { public virtual string TestProperty { get; set; } } // Перехватчик обращений к свойствам public class PropertyChangedInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { var proxy = invocation.Proxy as ViewModelBase; if (proxy != null && IsSetter(invocation.Method)) { // если происходит вызов сеттера свойства в VM // то получаем текущее значение свойства // и, если оно изменилось, вызываем PropertyChanged PropertyInfo property = GetPropertyBySetter(invocation.Method); object currentValue = property.GetValue(proxy, null); object newValue = invocation.Arguments[0]; if (IsModified(currentValue, newValue)) { invocation.Proceed(); proxy.RaisePropertyChanged(property.Name); } } else { invocation.Proceed(); } } public static bool IsModified(object currentValue, object newValue) { if (ReferenceEquals(currentValue, null)) return !ReferenceEquals(newValue, null); return !currentValue.Equals(newValue); } public static PropertyInfo GetPropertyBySetter(MethodInfo mi) { return mi.DeclaringType.GetProperties().FirstOrDefault(p => p.GetSetMethod() == mi); } public static bool IsSetter(MethodInfo mi) { return mi.DeclaringType.GetProperties().Any(p => p.GetSetMethod() == mi); } } 

Despite the fact that now there is no extra code in the presentation models, I still don’t like something myself. I would be grateful if someone criticizes this decision.

  • You cannot create view models through the constructor, only through the factory method. Creating a VM will have to be explicitly registered in code behind ( DataContext = ViewModelBase.Create<TestViewModel>() ). In this case, the constructor cannot be made private (otherwise an exception will be thrown in the ProxyGenerator ).
  • Calling property.GetValue(proxy, null) initiates a recursive call to the Intercept method (it seems that you can’t directly access the backing field of car ownership through reflection without hacks).
  • RaisePropertyChanged sticks out.
  • Performance issues