Interview with Simon Cropp of Fody and Notify Property Weaver
InfoQ. What originally prompted you to create Notify Property Weaver?
Simon Cropp: Like many people I was frustrated by the unnecessary noise code involved in implementing INotifyPropertyChanged. I dabbled in the various approaches (best outlined here) but was still unhappy with the various shortcomings. Then I stumbled across an article by Justin Angel titled "AutoMagically Implementing INotifyPropertyChanged".
This seemed like an ideal solution to me. However Justin's article was only the tip of the iceberg. Since it is only a proof of concept it isnot complete. For example
- it is configuration heavy
- does not work for all .net runtimes
- does not support inheritance
- lacking many features I wanted
- was not packaged and deliverable
So with Justin's blessing took his codebase and began Notify Property Weaver (NPW).
InfoQ: For our readers who are new to the topic, can you explain what IL Weaving is?
Simon Cropp: As a general definition I consider IL Weaving to be "Creating or manipulation IL without the use of a compiler".
This can happen at any of the following:
- Immediately after a compile e.g. NotifyPropertyWeaver and PostSharp
- Somewhere between. e.g. the resharper addin reflexil
- Before assembly load. If you dynamically load an assembly you can manipulate the IL before the file is locked by .net.
- Creating a new assembly at run time. e.g. XMLSerializer assemblies, mocking libraries and castle dynamic proxy
Each of these have various benefits and drawbacks. For my purposes I found "Immediately after a compile" to most closely meet my requirements.
I have attached two diagrams that show the "Immediately after a compile" approach.
So as you can see after the compiler has produced an assembly (IL) then MSBuild calls out to the ILWeaver. The ILWeaver then modifies the IL in any way you like. In my case I use Mono Cecil to do the low level IL manipulation.
InfoQ: So how does Notify Property Weaver use this?
Simon Cropp: The basic premise of NPW is two things.
- Derive where to inject code either through configuration or convention
- Inject the custom code into those locations
So mapping that to INPC we get
- Find all properties of types that implement INPC
- For each of those properties inject a call to OnPropertyChanged
With that being common knowledge I often get the question "So why does NPW have so much code?". The answer lies in my attempt to create a "Complete solution". So consider the following
- Generics classes
- Class hierarchies
- Base classes existing in other assemblies
- How to detect if a class implements INPC
- Loading the correct types for injecting code
Now all these things are easy at runtime with reflection they are more difficult when dealing with IL.
Then there are the extra features I have added. For example
- Detecting methods named other than OnPropertyChanged.
- Detecting dependent properties
- Calling a convention based method then a property is set
- Built in support for an "IsChanged" flag.
- Intercepting notification calls
All of these need to reverse engineer existing IL to function.
Now I don't mean to give the impression that ILWeaving is difficult. It just means you need to be methodical in your design and have code unit test coverage.
InfoQ: How much time was involved in creating this project?
Simon Cropp: It is very hard to tell. Much of the code and concepts I use in Notify Property Weaver is shared amongst my other IL Weaving projects. Since I have been working on it for 15 months I would approximate 200 hours.
InfoQ: Notify Property Weaver uses MSBuild Tasks. What are your thoughts in general about them? Do you think they're easy to use and create or are they more of an necessary evil?
Simon Cropp: MSBuild tasks fall into the category of "Make the simple thing difficult and the hard things impossible". Just this weekend I solved an MSBuild issue that has been annoying me for over a year.
The friction involved in working with MSBuild is unfortunate because it is the only way to reliably plug into the .net build pipeline.
I did experiment with building NPW as an executable and then wrapping them in an Exec Task. The theory being I could then fully abstract myself from MSBuild. Unfortunately this had several drawbacks
- Passing error and information messages back to MSBuild is cumbersome
- When exceptions occur it is harder to debug since you are stepping into another process
- An MSBuild Task gives you access to certain MSBuild contextual variables. The exe approach required that these always be passed through the Exec Task
- An exe will not work in MonoDevelop but an MSBuild Task will.
As for "MSBuild tasks being a necessary evil" I would be more generic and say "MSBuild is a necessary evil". To take a truly layered approach NPW should be a single assembly that is independent from MSBuild and could be called from any build engine. It is a sign of how dominant MSBuild and Visual Studio are that no one has ever asked for this.
I would have preferred the .net build system be split into three components
- The IDE. Visual Studio, MonoDevelop, SharpDevelop etc
- The definition for a solution and projects. This could be an xml spec as with MSBuild or perhaps some DSL
- The build tool. MSbuild, Nant or even something like Albacore https://github.com/derickbailey/Albacore
Each of these parts should be pluggable with any other.
InfoQ: Do you have any other projects that use IL Weaving?
So other projects where I have applied what I have learnt from NPW...
This is the project I am spending most time on now.
- It takes what I have learnt from NPW and applies a plugin based approach to ILWeaving.
- It has build time performance improvements over NPW.
- It is extensible both within a developers solution and through addins that are deployed through nuget.
Fody allows me to quickly churn out targeted ILWeaving tools without needing to worry about all the noise code of deployment, packaging, MSBuild integration and copied code.
Some blog posts using Fody
ModuleInit (Fody addin) - Adds a module initializer to an assembly.
PropertyChanged (Fody addin) - Injects INotifyPropertyChanged code into properties.
PropertyChanging (Fody addin) - Injects INotifyPropertyChanging code into properties.
Publicize (Fody addin) - Converts non-public members to public hidden members.
BTW the name "Fody" comes from this.
InfoQ: How much effort is invovled in building a Fody plugin?
Simon Cropp: Writing a Fody plugin depend very much on your experience and what you are trying to achieve. There are several knowledge domains that help in the process.
Plug-ins are deployed through Nuget. If you don't have experience with Nuget it would probably take an hour to learn how to create a package
- Knowledge of IL and Cecil
This is not a common thing to come by but is helpful when writing a plugin. Most people have a general idea of "what IL is" but not much in depth knowledge.
If someone is lacking in this area it is best to start with something simple.
- MSBuild Visual Studio
Fody integrates with a compile by plugging in to the MSBuild + Visual Studio ecosystem. So some knowledge of this ecosystem may be necessary to debug issues with addins. I believe I have abstracted most of this pain with how I have designed Fody, but some issues may still occur.
So if you are doing a very simple IL manipulation and are familiar with the moving pieces it is possible to write and deploy a Fody addin in under an hour.
Of course I don't consider any project production ready until I have set up all the standard things
- Source Control
- Automated Builds
So, for example, I am looking at starting a new project that uses IL to make unit testing easier though modifying assemblies to make the more unit testable. I expect this to take me approximately 6 hours to get a full working project up and deployed.
About the Author
Jonathan Allen has been writing news report for InfoQ since 2006 and is currently the lead editor for the .NET queue. If you are interested in writing news or educational articles for InfoQ please contact him at firstname.lastname@example.org.
York Xyander, Bodo Junglas Jul 31, 2015