📜 ⬆️ ⬇️

My experience with AOP and PostSharp

A long time ago, I researched PostSharp quite well and even started using it for my own development. He even managed to write a screencast and a couple of articles on this topic. Unfortunately, with PostSharp I didn’t happen at all afterwards, and for reasons of occasional requests from the community on “how? why? ”, I decided to write this post to dot the i.

Free cheese in a mousetrap


PostSharp allows you to stick functionality into existing code by rewriting IL-a. This idea is nothing new, and to write an MSBuild Task that will do ILDASM, change something in IL, and then do ILASM is a rather trivial task. At one time I did just that in order to remove the obfuscator metadata from the assembly. (It is still a mystery to me why obfuscators intentionally help the crackers by telling them exactly what they were used for.)

The essence of PostSharp is that you can declaratively take and mark, for example, a method, saying that each of its calls will occur in a separate thread:

[WorkerThread] public void MyMethod() { ⋮ } 

PostSharp in the post-compilation process takes this method and rewrites it, replacing a simple method call with some Task.Factory.StartNew(() => { /* */ }); . It looks good and, let's be honest, it works.
')
But there are a number of problems. For example, such code is difficult to manage. That is, I cannot suddenly take it and say that in a separate thread only Class A methods will now be executed, and Class B methods will be blocking. Moreover, I cannot really explain to users of my API that the method implements non-standard behavior — they will continue to think that the method blocks, and write asynchronous calls on top of it , which will not lead to anything good.

As for documenting, they can argue with me - they say once the attribute is present, then everything should be clear. But here everything is not so simple either - for example, I categorically dislike that the attribute is present at all after compilation. And that was the problem of PostSharp - he was trying to pull his library along, while a rational approach to the question suggests that in the good way no libraries are needed , because we have a local rewriting of IL, and that's it.

Habituation effect


I had a huge class marked with the attribute [NotifyPropertyChanged] - I think you already understood what he was doing. And everything would be as good, but in practice I was worried about several things.

First, the code for implementing INotifyPropertyChanged was stupid through the aspect - it was so complex and unreadable that I was afraid to touch it even when I needed it.

Secondly, at the compilation stage the object itself did not implement INotifyPropertyChanged and, accordingly, access to this interface had to be 'forged' through a double caste.

Third, the attribute approach was all-or-nothing. For example, I could receive notifications on the read-write properties, but when I wanted to change this property also generated NotifyPropertyChanged() on the read-only property dependent on it , problems started here.

As a result, I did a simple thing: I deleted PostSharp from the project and made the implementation of INPC through ReSharper . In other words, I stupidly automated the process of creating code, drove it through all the properties, and then manually corrected those from which I needed something special.

Alternatives?


It seems to me that anything is better than rewriting IL manually. The ideal for me is to use the compiler object model for manipulating structures, i.e. in fact, the approach for which the compiler is expanded. The programming language Boo allows you to do this, Nemerle too, but as for C # 5, then no one can give any guarantees - we do not know what metaprogramming mechanisms will be in the next version.

Another option is code generation. Its advantage is that at least you can see what is happening, because we only compile the code, without any post-build magic. Naturally, code generation works best when it uses some kind of parser like the one built into ReSharper. Through the ReSharper API, you can create very crazy things by manipulating code. For example, the problem of notifications for dependent variables is easily solved at the level of a separate daemon (analyzer), which can step along dependencies and tell us what needs to be added where.

Option three is to make your DSL. If the task does not fall on a simple C #, perhaps it has no place in it. Write DSL in the same F # , or in some other language, or use for example MPS to design this DSL and produce anything from it. Yes, this is the same code generation only from a different angle, but at least this approach is less deceptive: it does not try to squeeze incompatible concepts into the limitations of one or another language.

That's all. Please note that my attitude towards AOP / PostSharp is just my personal opinion. If everything works for you - it's great, write about it! ■

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


All Articles