📜 ⬆️ ⬇️

Fighting INotifyPropertyChanged or how I became an open source - 2

It all started the same as last time, quite prosaically: I had to develop * -13 ViewModels for my MVVM application.
In order for them to work optimally as ViewModel-i, my classes had to inherit from DependencyObject or implement the INotifyPropertyChanged (INPC) interface, which was broken down to holes.

It has long been no secret to anyone that DependencyProperty is more deceptive than the manual implementation of INPC. My tests show that writing to DependencyProperty is ~ 13 times slower than manual implementation. Therefore, I, as an incurable optimizer, tend exactly to INPC. Moreover, the INPC support code looks more logical and more organic than the description of DependencyProperties.


Many articles are written on how to facilitate the implementation of INPC. This is the variant with the StackTrace research, this is also the variant with Lambda-methods, this is also code-snippets as a personal code-monkey, this is also Resharper, as a panacea for refactoring errors. All of these options require a lot of unnecessary gestures, and I, as an irremovable routine affairs optimizer, do not like it.
')
Here, for example, is an implementation using StackTrace:

public sealed class StackTraceNPC : INotifyPropertyChanged { string _myProperty; public string MyProperty { get { return _myProperty; } set { if (_myProperty == value) return; _myProperty = value; RaisePropertyChanged(); } } ///       setter-  void RaisePropertyChanged() { var e = PropertyChanged; if (e != null) { var propName = new StackTrace().GetFrame(1).GetMethod().Name.Substring(4); e(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; } 


Here is an example with an expression tree, each time created by the compiler in the code:

  public sealed class LambdaNPC : INotifyPropertyChanged { string _myProperty; public string MyProperty { get { return _myProperty; } set { if (_myProperty == value) return; _myProperty = value; RaisePropertyChanged(() => this.MyProperty); } } void RaisePropertyChanged<T>(Expression<Func<T>> raiser) { var e = PropertyChanged; if (e != null) { var propName = ((MemberExpression)raiser.Body).Member.Name; e(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; } 


And here are the performance results of the above-mentioned INPC implementations:


Incorrigible optimizers, looking at these numbers, with horror erased from the memory and StackTrace, and Lambda options. It is clear that setters are not called so often to seriously think about their performance, but if we are talking about ViewModels to the DataGrid, or a serious number of fields, then these brakes can float to the surface. In addition, it is not so much a convenient call to RaisePropertyChanged, but more about optimizing all the hemorrhoids that are associated with it, including checking for a change in the field and other scribbling as a type property literal.

One of the worthy options would be an PostSharp-based AoP approach, but one glance through the Reflector on the IL-code received after compilation is enough to understand that we are not on the way with PostSharp either.

Here it would be time to twist ... But inspired by articles about Mono.Cecil about the injection of MSIL code into a third-party assembly using Mono.Cecil , I decided to solve this problem once and for all.

To begin with, I will give an example of how WAS :

 public class MyViewModel: PropertyChangedBase { string _stringProperty; public string StringProperty { get { return _stringProperty; } set { if (_stringProperty == value) return; _stringProperty = value; RaisePropertyChanged("StringProperty"); } } object _objectProperty; public object ObjectProperty { get { return _objectProperty; } set { if (_objectProperty == value) return; _objectProperty = value; RaisePropertyChanged("ObjectProperty"); } } } 


Now, an example of how it has become :

 public class MyViewModel: PropertyChangedBase { public string StringProperty { get; set;} public object ObjectProperty { get; set;} } 


And where is the implementation of INPC, you ask, and you will be right. Kind of Magic? Namely, the Kind of Magic MSBuild task. This is the name of this open-source codeplex project.

The whole secret is in the base class PropertyChangedBase, each of us has its own version :)

Let's see what is so special about him:

 [Magic] public abstract class PropertyChangedBase : INotifyPropertyChanged { protected virtual void RaisePropertyChanged(string propName) { var e = PropertyChanged; if (e != null) e(this, new PropertyChangedEventArgs(propName)); //      Dispatcher,     UI thread } public event PropertyChangedEventHandler PropertyChanged; } 


With the exception of the Magic attribute, everything else looks more or less in order. Let's look at the MagicAttribute, which is described in the same assembly as our MyViewModel class.

 class MagicAttribute: Attribute {} 


One line, you ask? Exactly. It is enough to define an attribute with the name MagicAttribute in your assembly, apply it to the base or any class that implements INPC. In this case, all public properties of these classes and their heirs will become INPC-compatible. You can apply directly to the properties of your INPC class, then only these properties will become INPC-compatible.

And adding this attribute:
 class NoMagicAttribute: Attribute {} 

You can exclude classes and properties from the magical implementation of INPC.

It’s not worth worrying about extra kilobytes of code, after compiling your build, there will be no trace of these attributes, to make sure Reflector will help you.

Now a little about how it works.



Well, now, we meet the winner:



Here you can download KindOfMagic , and here lies a test project for the doubters. Results obtained under Win7x64, Core2 Quad @ 2.4GHz.

UPDATE 1
Honestly, I did not expect such a devastating result, the resulting IL is not very much different.
On closer inspection, a bug was found, the bug was successfully fixed. The result of KindOfMagic equaled the handwritten code, as expected.

Real miracles do not happen, sometimes something as a result :)

Source: https://habr.com/ru/post/95211/


All Articles