What was new in PostSharp 4.2
In the previous blog post, I already announced the following improvements included in PostSharp 4.2 Preview 1:
- Support for Visual Basic
- Amazing performance improvements in ready-made patterns
- Other improvements in threading models
Today, I’m announcing the following new features:
- Thread-safety policy
- Ready-made patterns: support for System.Collections.Immutable
- Ready-made patterns: support for rules
- Module initializers
- Support for INotifyPropertyChanging
- OnAspectsInitialized advice
How we dogfooded our own threading models
Most of the improvements we are publishing in this release stem from dogfooding our threading models into PostSharp Tools for Visual Studio. I’m proud to say that we now have a multithreaded VS extension without a single lock or event. Now that all threading assumptions have been made explicit and declarative, the code is much cleaner and easier to understand.
Frankly, the experience was both exciting and awkward. Exciting because the result is amazing. Awkward because we filed more than 50 bugs, enhancements and user stories in the process – principally usability issues or improvements. Most of these tickets are addressed in PostSharp 4.2 Preview 3.
Thread-safety policy
Suppose that you have a large solution with hundreds of classes and you want to apply PostSharp thread safety to it. Typically, you would go through all classes and apply one of the PostSharp threading models. But how do you know you didn’t forget anything?
The short answer:
[assembly: ThreadSafetyPolicy]
When added to a project, the thread-safety policy shall emit a warning whenever it detects:
- A class without threading model
- A static field that is mutable or of a non thread-safe type.
Having none of these warnings is not a 100% guarantee that your application is thread-safe. However, it catches the most common omissions. Once a class is assigned to a threading model, thread safety becomes the responsibility of the threading model itself, not of the thread-safety policy.
The thread-safety policy is built using PostSharp Architecture Framework.
Ready-made patterns: support for System.Collections.Immutable
Let’s face it: immutability is a great concept. It allows for excellent runtime performance and, most importantly, makes it easier to think about the semantics of your API. PostSharp provides the [Immutable] threading model to ensure your custom classes are immutable. Previously, you should have used an AdvisableCollection inside your immutable objects. However, the AdvisableCollection class is far from being as fast and convenient as immutable collections. That’s why we added genuine support for System.Collections.Immutable in PostSharp ready-made patterns.
So you can now mix PostSharp’s threading models with Microsoft’s immutable collections to build code that’s thread-safe, highly readable, and still fast enough.
Here is an example of class that has been ported to [Immutable]. Note that we did not completely refactor the solution to use immutable collections, that’s why inputs and outputs of the class are still mutable arrays.
[Immutable] public class BasicWizardProcess : IWizardProcess { [Child(ItemsRelationship = RelationshipKind.Reference)] private ImmutableArray wizardParts; public BasicWizardProcess( params IWizardPart[] wizardParts ) { this.wizardParts = ImmutableArray.Create(wizardParts); } public IWizardPart[] GetParts( IWizardController controller ) { foreach ( IWizardPart part in this.wizardParts ) { part.Controller = controller; } return this.wizardParts.ToArray(); } }
Ready-made patterns: support for rules
Threading models and undo/redo require you to annotate your object model with a parent-child structure using the [Child] and [Reference] attributes. When you have a reference-type field, PostSharp needs to know whether it is a reference or a child (i.e. whether it is an association or an aggregation/composition). As a design decision, PostSharp does not assume that unannotated fields are automatically references.
There are times when this design is unusable. For instance, there is no way to annotate fields generated by the WinForms or XAML designers.
Starting from PostSharp 4.2, it becomes possible to assign the role of a field programmatically, by rule. A rule is simply a class derived from FieldRule. You need to register the rule using the [RegisterFieldRule] custom attribute.
The following code shows our internal rules for WinForms. Note that this rule is automatically registered so there’s no need for [RegisterFieldRule] in this specific case.
internal sealed class WinFormsFieldRule : FieldRule { public override RelationshipKind? GetRelationshipKind(FieldInfo field) { if ( IsDerivedFrom(field.DeclaringType, "System.Windows.Forms.Control") && ( field.Name == "components"
|| IsDerivedFrom( field.FieldType, "System.Windows.Forms.Control") )) { return RelationshipKind.Reference; } else { return null; } } }
The benefits of rules is that you don’t have to explicitly use custom attributes everywhere. We will continue to require custom attributes if no rule is defined because we believe it’s a good practice to force developers to think about the role of a field. However, you can now easily implement a rule that makes all fields references by default. Note that custom attributes always have priority over rules.
Note also that rules are not yet integrated with the UI.
Module initializers
Ever wanted to execute code immediately when an assembly is loaded? Static constructors are not enough: you need a module initializer. VB has them, but C# does not. PostSharp 4.2 fixes this with the [ModuleInitializer(int)] custom attribute.
Just add the custom attribute to a static public/internal void parameterless method. Here is an example. It also demonstrates how we test PostSharp internally:
public static class Program { static StringBuilder trace = new StringBuilder(); public static int Main() { if (trace.ToString() != "0;1;2;") return 1; return 0; } [ModuleInitializer(0)] public static void Method0() { trace.Append("0;"); } [ModuleInitializer(1)] public static void Method1() { trace.Append("1;"); } [ModuleInitializer(2)] public static void Method2() { trace.Append("2;"); } }
Support for INotifyPropertyChanging
PostSharp has supported INotifyPropertyChanged for a long time now. It now also supports INotifyPropertyChanging.
The feature is a bit tricky because INotifyPropertyChanging is not portable, but our [NotifyPropertyChanged] aspect is. Here is how to proceed:
- Your target class needs to implement INotifyPropertyChanging manually and it has to define a void OnPropertyChanging(string) method (typically protected). PostSharp will emit the event through the OnPropertyChanging method.
- If you want to support dependencies of objects that implement INotifyPropertyChanging but do not have the [NotifyPropertyChanged] aspect, you need to implement an INotifyPropertyChangingAdapter and register it using NotifyPropertyChangedServices.PropertyChangingAdapter.
The procedure is a bit cumbersome so we (or you) could create a non-portable [NotifyPropertyChanging] aspect that would encapsulate this for a specific platform.
OnAspectsInitialized advise
How can you execute code after all aspects on a class have been initialized? Previously, you could not do that in a simple and reliable way. We decided to fix that, simply, because we needed it to fix a few bugs :) The solution is now to use [OnAspectsInitializedAdvice]:
[OnAspectsInitializedAdvice] public void OnAspectsInitialized(AspectInitializationReason reason) { }
Summary
PostSharp 4.2 makes threading models usable in complex, real-world applications. We ate our own dogfood. We were scared by the number of small usability issues we encountered, but so relieved that our initial design was correct and robust.
Happy PostSharping!
-gael