Unity Container (2) Implementing the INotifyPropertyChanged Interface

Advanced Unity Container usages

Posted by eagleboost on April 6, 2021

1. The problem

The INotifyPropertyChanged interface is probably one of the most famous interfaces in the interface oriented programing history. It has been used extensively almost everywhere, especially UI applications.

Because it’s so popular, developers have to spend time to keep writing similar codes for properties. A typical ViewModel needs to contain at least below codes or equivalent logic, even when it has only one property, and as we can see that the property setter is the most tedious piece among all these codes.

Developers can use [CallerMemberName] to save the explicit passing of the property name, but it only works in .Net 4.0 and later versions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ViewModel : INotifyPropertyChanged
{
  private string _name;
  
  public string Name
  {
    get => _name;
    set
    {
      if (_name != value)
      {
        _name = value;
        NotifyPropertyChanged();
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private void NotifyPropertyChanged([CallerMemberName] string name = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  }
}

So various ways have been created to simplify the process of writing a ViewModel:

  1. Put the common codes into a base class such that the child classes can focus on property Getter and Setter. But it does not work for the case that base class cannot be changed.
  2. Extract Property Setter into some generic method like SetValue and use EqualityComparer<T>.Equals() to do the value difference check. This can help simplify the property setter code to one line.
  3. Dynamically generate codes.

Since the pattern to implement the INotifyPropertyChanged interface and raise event in the property setter is pretty common, so if things come true obviously auto-generated codes can simplify the efforts of writing classes like ViewModel.

2. InterceptionBehavior

The Interception in Unity Container is one of the ways to simplify the codes. One of the Unity series articles on Microsoft docs Implement INotifyPropertyChanged Example provides a demo implementation. Assume ViewModel and its interface are like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IViewModel
{
  string Name { get; set; }
  
  int Age { get; set; }
}

public class ViewModel : IViewModel
{
  public string Name { get; set; }
  
  public int Age { get; set; }
}

We can use InterceptionBehavior to intercept the calls to property setters and add/remove to the PropertyChanged event to implicitly implement the INotifyPropertyChanged interface.

1
2
3
4
5
6
var container = new UnityContainer();
container.AddNewExtension<Interception>();

var interceptor = new Interceptor<InterfaceInterceptor>();
var behavior = new InterceptionBehavior<NotifyPropertyChangedBehavior>();
container.RegisterType<IViewModel, ViewModel>(interceptor, behavior);

It works, but has problems:

  1. Unity Container actually creates a new class dynamically to implement the same interface and wrap the original be intercepted class, so the IViewModel interface is required, otherwise Unity Container would throw exceptions of “Type is not interceptable”.
  2. Because event is a special delegate that can only be called from inside of a class, NotifyPropertyChangedBehavior actually provides its own PropertyChanged event to support add/remove of the event listeners. So listen to the property changes only works from outside of the class, event listeners attached from inside the class won’t work because the event is not used at all.

3. Reflection.Emit

The ultimate solution is to dynamically generate codes. One way is to generate codes at run time via Reflection.Emit, another way is to run some MSBuild post process tasks to generate codes based configurations like attribute and edit the assemblies. AOP libraries like PostSharp work in this way. In the end these two approaches are both manipulating IL, but we’ll only talk about the first approach in this blog. For the InterceptionBehavior above, Unity Container internally also creates a Proxy class via Reflection.Emit, just that its purpose is to expose something to allow developers put custom logic in the behaviors.

PostSharp also provides support to implement INotifyPropertyChanged, but PostSharp is not free and its approach is similar to what we’re going to talk about.

So write our own codes is the best way to properly generate implementations for the INotifyPropertyChanged interface.

  1. No need to define interface for the source class.
  2. Allow attaching PropertyChanged event listeners from inside and outside of the class.

This article Dynamically generating types to implement INotifyPropertyChanged gives a relatively complete implementation. It creates a dynamic class inheriting from the existing class (so no interface is needed), the existing class just need to mark the properties as virtual so they can be overridden.

I have created a project to integrate the codes with Unity Container and put on github. As long as a class implement the IAutoNotifyPropertyChanged interface (any other way is also fine), when being resolved by the Container, a dynamic class would be created with INotifyPropertyChanged interface implemented.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NotifyPropertyChangedImplStrategy : BuilderStrategy
{
  public override void PreBuildUp(ref BuilderContext context)
  {
    base.PreBuildUp(ref context);

    if (context.Type.IsSubclassOf<IAutoNotifyPropertyChanged>())
    {
      var implType = NotifyPropertyChangedImpl.GetType(context.Type);
      context.Type = implType; 
    }
  }
}

4. There’re still problems

Although the Reflection.Emit approach can save us repeated works, but there’re still rooms to improve:

First of all, it’s not debug friendly. Since the property setter is emitted IL codes, so there’re no source codes to set break points in the setter, there’re workarounds but not convenient.

Second, the INotifyPropertyChanged interface is a very high level abstraction, so it might cause problems if not used properly:

  1. Performance impact. Say if we’re only interested in changes of several properties, we need to match the property name in the event handler. If the object has many properties then the event handler would be called many times because of irrelevant property changes, it can cause performance problems in extreme cases.
  2. Inconvenience. There’re cases we want to know the previous and current value of the property, one common way is letting the object implement the INotifyPropertyChanging interface, then we subscribe to it and store previous value of the properties. It works but apparently not quality coding.

There should be better solutions to fix these problems.

1. 问题

如果说面向接口编程的历史上接口的著名程度有一个排名,INotifyPropertyChanged排前三应该没有太大争议。INotifyPropertyChanged高度抽象化了对象的“属性发生改变”这一行为,在应用程序尤其是包含UI的应用程序中可以说无处不在。

然而正因为其无处不在,开发人员不得不耗费大量时间来编写重复的代码。就算是只包含一个属性的ViewModel也至少需要包含下面的代码或等价的逻辑,其中以Property Setter的编写尤其繁琐。

.Net 4.0以后的版本中已经可以通过[CallerMemberName]来省去对属性名的重复。在早期版本中每个调用NotifyPropertyChanged的地方还需要显式传递属性名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ViewModel : INotifyPropertyChanged
{
  private string _name;
  
  public string Name
  {
    get => _name;
    set
    {
      if (_name != value)
      {
        _name = value;
        NotifyPropertyChanged();
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private void NotifyPropertyChanged([CallerMemberName] string name = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  }
}

聪明的开发人员想出了各种办法来简化ViewModel的编写过程:

  1. 把基本的实现放到一个基类中,子类只需要实现属性的GetterSetter。但不能解决基类不能更改的情况。
  2. Property Setter的代码提取到一个泛型方法比如SetValue中,用EqualityComparer<T>.Equals()来代替值的比较,这种方法可以把Setter简化到一行代码。
  3. 自动生成代码。

由于INotifyPropertyChanged接口的实现以及触发属性变化的过程对每个类来说都是类似的逻辑,自动生成代码如果能够实现显然会带来简化的效果。

2. InterceptionBehavior

比较简单的一种方法是使用Unity Container中的Interception。Microsoft docs 上Unity系列文章里有一篇Implement INotifyPropertyChanged Example给出了一种实现。假设ViewModel及其接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IViewModel
{
  string Name { get; set; }
  
  int Age { get; set; }
}

public class ViewModel : IViewModel
{
  public string Name { get; set; }
  
  public int Age { get; set; }
}

可以通过InterceptionBehavior来截获对属性的Setter以及添加删除PropertyChanged事件订阅的调用来隐式实现INotifyPropertyChanged接口,具体实现可以参考这里

1
2
3
4
5
6
var container = new UnityContainer();
container.AddNewExtension<Interception>();

var interceptor = new Interceptor<InterfaceInterceptor>();
var behavior = new InterceptionBehavior<NotifyPropertyChangedBehavior>();
container.RegisterType<IViewModel, ViewModel>(interceptor, behavior);

这种方法基本可行,但存在不小的缺陷:

  1. Unity Container实际上创建了一个实现相同接口的类来封装现有类,所以接口IViewModel是必须的,否则会抛出”Type is not interceptable“的异常。
  2. 由于事件是只能在一个类的内部调用的特殊委托,NotifyPropertyChangedBehavior实际上自己定义了一个PropertyChanged事件来替代被截获类的PropertyChanged事件,因此监听属性改变只对外部代码有效,任何在类的内部监听属性改变的尝试都是徒劳的,因为被监听类自己的PropertyChanged事件根本没有被用到。

3. Reflection.Emit

终极的解决方案是动态生成代码。一种方法是在运行时通过Reflection.Emit动态生成代码,另一种是在MSBuildpost process阶段根据Attribute之类的配置生成代码并修改AssemblyPostSharp之类AOP库使用的就是第二种方法。两种方法归根到底都是操作IL代码,本文只讨论在运行时生成代码的方式。实际上在上述Interception方法里Unity Container也使用Reflection.Emit动态生成了一个Proxy类,只不过其目的是为了允许开发人员添加自定义代码。

PostSharp对实现INotifyPropertyChanged接口也提供了支持,不过PostSharp并非免费,其实现也与我们将要提到的方法大同小异。

为了完美地自动实现INotifyPropertyChanged接口,最好的办法是自己生成代码:

  1. 无需定义接口。
  2. 可在类的外部和内部监听PropertyChanged事件。

这篇文章Dynamically generating types to implement INotifyPropertyChanged就给出了一个相对完整并可用的实现——动态生成一个类来继承现有类,所以无需接口,唯一的要求是把属性标记为virtual以便动态生成代码来重载Property Setter

我把整理了代码并与Unity Container简单整合了一下放到了github——通过一个IAutoNotifyPropertyChanged接口(也可以用别的方法)来标记一个类,一旦检测到该接口的存在就创建动态一个类来实现INotifyPropertyChanged接口并重载所有属性的Setter

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NotifyPropertyChangedImplStrategy : BuilderStrategy
{
  public override void PreBuildUp(ref BuilderContext context)
  {
    base.PreBuildUp(ref context);

    if (context.Type.IsSubclassOf<IAutoNotifyPropertyChanged>())
    {
      var implType = NotifyPropertyChangedImpl.GetType(context.Type);
      context.Type = implType; 
    }
  }
}

4. 还有问题

虽然动态生成代码的方法帮助我们节省了不少重复劳动,但还有改进的空间:

首先代码调试不方便。动态生成的类EmitIL代码来重载属性的Setter,所以没有源代码也无法直接添加断点进行调试,只能通过添加事件处理函数的方式来绕道而行。

其次,前面说过INotifyPropertyChanged接口高度抽象了属性发生改变的行为,这一高度抽象一定程度上也带来了两个问题:

  1. 性能损失——假如我们只希望监听某几个属性的改变,只能在PropertyChanged事件处理函数里检查属性名是否匹配。如果被监听的对象属性众多那么事件处理函数会因为无关属性的改变被不必要地调用很多次,极端情况会带来性能问题。
  2. 缺乏便利性——有时候我们需要在事件触发时知道属性的当前值和之前的值。原生的做法是让对象实现另一个INotifyPropertyChanging接口,然后监听其PropertyChanging事件来记录属性之前的值。这可行但不是好的代码。

因此我们需要一套更好的实现来改进这几个缺陷……