Yet another AOP in .NET
Many .NET developers who used WPF, Silverlight, or Metro UI in their practice, one way or another, asked the question "how can you simplify your implementation of the INotifyPropertyChanged interface and properties that need to be signaled for changes?".
The simplest “classic” option for describing a property that supports notification of its change looks like this:
In order not to repeat similar lines in each setter, you can make an auxiliary function. Moreover, starting with .NET 4.5, you can use the [CallerMemberName] attribute in it so that you do not explicitly specify the name of the property that calls it. But this does not solve the main problem - all the same, for each new property, it is necessary to explicitly describe the field and getter with the setter. Such mechanical work is uninteresting, tiring and can lead to errors in copying and pasting.
I would like a little “magic”, which allows us to make such a class compatible with INotifyPropertyChanged with a little effort (for example, one line of code):
It should be noted that this is not the only task in which I would like to simplify my life by adding cross-cutting functionality. Typical tasks of logging called methods of a certain class, measuring their runtime, checking the availability of rights to call are all that require a common solution, eliminating the need to write similar pieces of code in every place where it is needed.
A more or less experienced developer will immediately say - “This is aspect-oriented programming!” And he will be right. And if you start listing already existing libraries for the .NET platform, in which it is possible to use AOP to one degree or another, the list will not be so short: PostSharp, Unity, Spring .NET, Castle Windsor, Aspect .NET ... And this far from all, but here you should think about the mechanisms that implement the insert of end-to-end functionality, about their advantages and disadvantages. Two main methods can be distinguished:
Substitution during compilation is the most profitable way, since it does not require any additional computational power when running the program, which is especially important for mobile devices. The usual generation of proxy classes, although simpler to implement, also has limitations in addition to computational costs - methods or properties must be contained in the interface or be virtual so that they can be intercepted through the proxy class.
PostSharp offers very great opportunities for using aspect-oriented programming, but it is a commercial product that for many projects may not be acceptable. As an alternative, we have developed and continue to improve the Aspect Injector.- A framework that allows you to apply aspects at the compilation stage and has a simple, but at the same time flexible interface.
The simplest example of using Aspect Injector is to log method calls. First you need to describe the class of the aspect:
This description suggests that when this aspect is applied to any other class, at the beginning of each public method, a call to Trace () will be added with the name of the method itself as a parameter.
After such an announcement, Create, Update, Delete will print their names to the console with each call. It should be noted that the Aspect attribute can be applied not only to classes, but also to specific members of the class if a “point insertion” is needed. Here is an example of the decompiled code obtained after compiling the example above:
Here you can also notice that the Aspect attribute was removed during code generation, which allows the resulting assembly not to refer to the Aspect Injector assembly.
If you return to the original task of implementing the INotifyPropertyChanged interface, using Aspect Injector you can create and successfully use the following aspect:
All public properties of all classes to which this aspect will be bound will have RaisePropertyChanged executed at the end of the setter. Moreover, the interface specified in the attribute AdviceInterfaceProxy will be added to the class by the framework itself at compile time.
On the project page you can find more detailed information about the attributes and their parameters that are currently available. We will be grateful for any feedback and suggestions for the development of Aspect Injector!
The simplest “classic” option for describing a property that supports notification of its change looks like this:
public string Name
{
get { return _name; }
set
{
if(_name != value)
{
_name = value;
NotifyPropertyChanged(“Name”);
}
}
}
In order not to repeat similar lines in each setter, you can make an auxiliary function. Moreover, starting with .NET 4.5, you can use the [CallerMemberName] attribute in it so that you do not explicitly specify the name of the property that calls it. But this does not solve the main problem - all the same, for each new property, it is necessary to explicitly describe the field and getter with the setter. Such mechanical work is uninteresting, tiring and can lead to errors in copying and pasting.
I would like a little “magic”, which allows us to make such a class compatible with INotifyPropertyChanged with a little effort (for example, one line of code):
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birth { get; set; }
}
It should be noted that this is not the only task in which I would like to simplify my life by adding cross-cutting functionality. Typical tasks of logging called methods of a certain class, measuring their runtime, checking the availability of rights to call are all that require a common solution, eliminating the need to write similar pieces of code in every place where it is needed.
A more or less experienced developer will immediately say - “This is aspect-oriented programming!” And he will be right. And if you start listing already existing libraries for the .NET platform, in which it is possible to use AOP to one degree or another, the list will not be so short: PostSharp, Unity, Spring .NET, Castle Windsor, Aspect .NET ... And this far from all, but here you should think about the mechanisms that implement the insert of end-to-end functionality, about their advantages and disadvantages. Two main methods can be distinguished:
- Compilation Substitution (PostSharp)
- Generating proxy classes at runtime (Unity, Spring .NET, Castle Windsor)
Substitution during compilation is the most profitable way, since it does not require any additional computational power when running the program, which is especially important for mobile devices. The usual generation of proxy classes, although simpler to implement, also has limitations in addition to computational costs - methods or properties must be contained in the interface or be virtual so that they can be intercepted through the proxy class.
PostSharp offers very great opportunities for using aspect-oriented programming, but it is a commercial product that for many projects may not be acceptable. As an alternative, we have developed and continue to improve the Aspect Injector.- A framework that allows you to apply aspects at the compilation stage and has a simple, but at the same time flexible interface.
The simplest example of using Aspect Injector is to log method calls. First you need to describe the class of the aspect:
public class MethodTraceAspect
{
[Advice(InjectionPoints.Before, InjectionTargets.Method)]
public void Trace([AdviceArgument(AdviceArgumentSource.TargetName)] string methodName)
{
Console.WriteLine(methodName);
}
}
This description suggests that when this aspect is applied to any other class, at the beginning of each public method, a call to Trace () will be added with the name of the method itself as a parameter.
[Aspect(typeof(MethodTraceAspect))]
public class Target
{
public void Create() { /* … */ }
public void Update() { /* … */ }
public void Delete() { /* … */ }
}
After such an announcement, Create, Update, Delete will print their names to the console with each call. It should be noted that the Aspect attribute can be applied not only to classes, but also to specific members of the class if a “point insertion” is needed. Here is an example of the decompiled code obtained after compiling the example above:
public class Target
{
private readonly MethodTraceAspect __a$_MethodTraceAspect;
public void Create()
{
this.__a$_MethodTraceAspect.Trace("Create");
}
public void Update()
{
this.__a$_MethodTraceAspect.Trace("Update");
}
public void Delete()
{
this.__a$_MethodTraceAspect.Trace("Delete");
}
public Target()
{
this.__a$_MethodTraceAspect = new MethodTraceAspect();
}
}
Here you can also notice that the Aspect attribute was removed during code generation, which allows the resulting assembly not to refer to the Aspect Injector assembly.
If you return to the original task of implementing the INotifyPropertyChanged interface, using Aspect Injector you can create and successfully use the following aspect:
[AdviceInterfaceProxy(typeof(INotifyPropertyChanged))]
public class NotifyPropertyChangedAspect : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (s, e) => { };
[Advice(InjectionPoints.After, InjectionTargets.Setter)]
public void RaisePropertyChanged(
[AdviceArgument(AdviceArgumentSource.Instance)] object targetInstance,
[AdviceArgument(AdviceArgumentSource.TargetName)] string propertyName)
{
PropertyChanged(targetInstance, new PropertyChangedEventArgs(propertyName));
}
}
All public properties of all classes to which this aspect will be bound will have RaisePropertyChanged executed at the end of the setter. Moreover, the interface specified in the attribute AdviceInterfaceProxy will be added to the class by the framework itself at compile time.
On the project page you can find more detailed information about the attributes and their parameters that are currently available. We will be grateful for any feedback and suggestions for the development of Aspect Injector!