Skip to content

Latest commit

 

History

History

change-tracking-3

uid
sample-dirty-3

Change Tracking example, step 3: integrating with INotifyPropertyChanged

[!metalama-project-buttons .]

At this point, we have a TrackChanges aspect that implements the xref:System.ComponentModel.IChangeTracking interface, supports hand-written base implementations of this interface and reports errors if the pattern contract is not respected. However, we have built this aspect in pure isolation. In practice, the TrackChanges aspect must interact with the NotifyPropertyChanged pattern. When the xref:System.ComponentModel.IChangeTracking.IsChanged property changes, the xref:System.ComponentModel.INotifyPropertyChanged.PropertyChanged event must be raised.

It is essential to understand that the two concepts interacting with each other are not aspects or interfaces but patterns. Aspects, by definition, are executable artifacts that automate the implementation and verification patterns, but patterns can also be implemented manually. Patterns define extension points. The OnPropertyChanged method is a part of the pattern we chose to implement the xref:System.ComponentModel.INotifyPropertyChanged interface, but not a part of the interface itself. Patterns are essentially conventions, and a different implementation pattern can rely on a different triggering mechanism than the OnPropertyChanged method.

Therefore, when you design an aspect, you should first reason about the pattern, think about how the different patterns combine, and how they work with inherited classes or parent-child relationships.

For this example, we decide (and we insist this is a design pattern decision) to invoke the OnChange method from the OnPropertyChanged method. Why? There are two reasons for this. First, the setters of all mutable properties are already supposed to call the OnPropertyChanged method, so adding a new call to OnChange everywhere would be a double pain. This argument is valid if we implement the pattern by hand, but what if we use an aspect? Here comes the second reason: the code generated by Metalama is much less readable when two aspects are added to one property.

Let's see this pattern in action:

[!metalama-files Comment.cs ModeratedComment.cs]

Aspect implementation

The new aspect implementation is the following:

[!metalama-file TrackChangesAttribute.cs]

Notice the new GetOnPropertyChangedMethod method. It looks for the OnPropertyChanged method in the xref:Metalama.Framework.Code.INamedType.AllMethods collection. This collection contains methods defined by the current type and the non-private ones of the base classes. Therefore, GetOnPropertyChangedMethod may return an xref:Metalama.Framework.Code.IMethod from the current type, from the base class, or null.

[!metalama-file TrackChangesAttribute.cs member="TrackChangesAttribute.GetOnPropertyChangedMethod"]

We call GetOnPropertyChangedMethod from BuildAspect.

If we do not find any OnPropertyChanged, we have to override all fields and automatic properties ourselves:

[!metalama-file TrackChangesAttribute.cs marker="NoOnPropertyChanged"]

However, if the closest OnPropertyChanged method is in the base type, the logic is more complex:

[!metalama-file TrackChangesAttribute.cs marker="NoOnPropertyChanged"]

If the closest OnPropertyChanged is in the current type, we override it:

[!metalama-file TrackChangesAttribute.cs marker="OnPropertyChangedInBaseType"]

If both the OnPropertyChanged method and the ISwitchableChangeTracking interface are defined in the base type, we do not have to hook OnPropertyChanged because it is the responsibility of the base type. We rely on the outcome of the xref:Metalama.Framework.Advising.IAdviceFactory.ImplementInterface* method to know if ISwitchableChangeTracking was already implemented.

However, if the base type defines an OnPropertyChanged method but no ISwitchableChangeTracking interface, we need to override the OnPropertyChanged method. It's only possible if the base method is virtual. Otherwise, we report an error. To override a base class method, we need to use xref:Metalama.Framework.Advising.IAdviceFactory.IntroduceMethod* instead of xref:Metalama.Framework.Advising.IAdviceFactory.Override*.

Finally, we also need to change the implementations of IsTrackingChanges and OnChange to call OnPropertyChanged. Let's see, for instance, OnChange:

[!metalama-file TrackChangesAttribute.cs member="TrackChangesAttribute.OnChange"]

If the OnPropertyChanged method is present, we invoke it using the xref:Metalama.Framework.Code.Invokers.IMethodInvoker.Invoke* method. Note, to be precise, that xref:Metalama.Framework.Code.Invokers.IMethodInvoker.Invoke* does not invoke really the method because the code runs at compile time. What it actually does is generate the code that will invoke the method at run time. Note also that we cannot use the conditional ?. operator in this case. We must use an if statement to check if the OnPropertyChanged method is present.

Summary

In this article, we briefly discussed the philosophy of pattern interactions. We then integrated the TrackChanges and the NotifyPropertyChanges pattern. In the following article, we will add the ability to revert changes done to the object.

[!div class="see-also"] xref:sample-notifypropertychanged