Click here to Skip to main content
15,886,110 members
Articles / All Topics

PostSharpin’ – Part 3

Rate me:
Please Sign up or sign in to vote.
4.86/5 (3 votes)
19 May 2014CPOL5 min read 11.9K   3   5
In the final part of this series, I look at new features coming in PostSharp 3.2, including support for aggregates and undo/redo.

In the final part of this series, I look at new features coming in PostSharp 3.2, including support for aggregates and undo/redo.

Aggregates

Under the hood, the biggest new feature in 3.2 might be the support for aggregates: object graphs with parent-child relationships. Version 3.2 makes aggregates first class citizens in the world of PostSharp aspects, and allows PostSharp to offer more complex features like undo/redo. They’ve also modified other aspects to be aggregate aware – so for example, the Actor aspect now also implements IAggregatable.

You mark up properties in your aggregatable types with Child, Parent and Reference aspects, and PostSharp then does the right thing when dealing with your object graph. I mentioned an “aggregatable type” – you can mark up your class with the Aggregatable aspect, but on its own this won’t do much. Instead, you’ll use another instance-level aspect – such as Recordable, Immutable, Disposable, and others – which are all aggregate aware and will work correctly with your object graph.

If you’re using Entity Framework, nHibernate, or similar, these frameworks already understand your graph and its relationships, so additional markup may feel like more work, although these aspects could open the door to custom aspects which understand both the data services layer and composition of your model.

Here’s a simple example of aggregates with the new Disposable aspect, which handles the dirty work of implementing IDisposable on types in your graph.

C#
using PostSharp.Patterns.Collections;
using PostSharp.Patterns.Model;

[Disposable]
public class Order
{
    public Order()
    {
        Details = new AdvisableCollection<OrderDetail>();
    }

    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public string Customer { get; set; }
    
    [Child]
    public ICollection<OrderDetail> Details { get; private set; }
}

[Disposable]
public class OrderDetail
{
    public int Id { get; set; }
    public string Product { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
} 

The child collection must be of type AdvisableCollection, otherwise PostSharp raises a runtime error. But once defined correctly, when the parent is disposed PostSharp will dispose of all children too.

A bit irritating, though, is that to use your parent type in a using statement you must initialize it outside the scope of the using to avoid the build-time error type used in a using statement must be implicitly convertible to System.IDisposable:

C#
var order = new Order { Id = 1, OrderDate = DateTime.Now };
using (order as IDisposable)
{
   ...
}

Immutable and Freezable

I was initially excited to see that PostSharp will be adding Immutable and Freezable aspects in version 3.2.

When you need to support thread-safe access for a number of object types, the idea of immutable types is really appealing. But also attractive is the use of object initializers. Unfortunately, since object initializers require public setters, the type can’t be immutable. Named constructor parameters can partially solve the issue, but the brevity of object initializers, for both the caller and callee, can’t be beat. Fortunately, C# 6.0 should make writing immutable types easier with the new primary constructors and property initializers, but I was hoping that PostSharp could work some magic here right now, without having to wait for the C# release. Well, this may be a pipe dream of mine. PostSharp will be adding support for early and late object initialization through Immutable and Freezable, but these address the problem with regard to object graphs and “deep” immutability. Granted, this will be a helpful feature. Unfortunately, I wasn't able to get these aspects working correctly with the alpha code so I'll have to try again later.

Edit: Per Gael Fraiteur's recommendation, an upgrade to version 3.2.20-alpha got this working. As hoped, PostSharp will raise an ObjectReadOnlyException if you try to make any changes to an Immutable type after construction or a Freezable type after Freeze is called. This works for both simple and "deep" fields and properties. I expect "freezability" to be especially useful.

These aspects are available from the pre-release version of the PostSharp Threading Pattern Library. A more thorough discussion of these aspects is available here.

Recordable

Now here is something to get excited about. Implementing the Memento pattern on your own is usually hard, error prone, and non-performant; probably the reason why so few applications and frameworks support it, but also making it a great candidate for an AOP approach.

The Recordable aspect builds upon the support for aggregation, so with a few simple changes to the code above I’m ready for Undo/Redo support:

C#
using PostSharp.Patterns.Collections;
using PostSharp.Patterns.Model;
using PostSharp.Patterns.Recording;

[Recordable]
public class Order
{
    public Order()
    {
        Details = new AdvisableCollection<OrderDetail>();
    }

    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public string Customer { get; set; }

    [Child]
    public ICollection<OrderDetail> Details { get; private set; }
}

[Recordable]
public class OrderDetail
{
    public int Id { get; set; }
    public string Product { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

As before, the child collection must be an AdvisableCollection, otherwise an error is thrown.

Once a type, or the types in an object graph, are made recordable, you then use the new RecordingServices features. The Recorder tracks operations within a scope and provides undo/redo functions. There’s a default Recorder, RecordingServices.DefaultRecorder, to get started or for simple applications.

By default, every change to a property or field, add/remove from a child collection, or call to a
public method on the class, is atomic. To bundle these into a scope – a logical operation – you use a RecordingScope, which can be set either declaratively or programmatically.

For example:

C#
var order = new Order();
using (RecordingScope scope = RecordingServices.DefaultRecorder.OpenScope(RecordingScopeOption.Atomic))
{
    order.Customer = "Bikes R Us";
    order.Id = 1;
    order.OrderDate = DateTime.Now;
}

If Undo were called after the above, the Order object would be returned to its default state after construction.

It’s worth noting that using an object initializer is not an atomic operation. For example, the following will undo the last property set operation (the set of OrderDate):

C#
var order = new Order { Customer = "Bikes R Us", Id = 1, OrderDate = DateTime.Now };
RecordingServices.DefaultRecorder.Undo();

Also notable is the factoid that constructors do not participate in an operation, even if the constructor sets properties or fields within the class; the object must be initialized before it can be considered recordable.

Along with Undo is of course Redo.

Here we create a new Order and OrderDetail, Undo the add of the detail line to the order, and then immediately have a change of heart and call Redo to restore the added line:

C#
var order = new Order { Customer = "My Grocer", Id = 1, OrderDate = DateTime.Now };
var od = new OrderDetail { Id = 1, Product = "pears", Quantity = 10, UnitPrice = 1.99M };
order.Details.Add(od);
RecordingServices.DefaultRecorder.Undo();
RecordingServices.DefaultRecorder.Redo();

Restore points are supported too:

C#
var order = new Order { Customer = "My Grocer", Id = 1, OrderDate = DateTime.Now };
RecordingServices.DefaultRecorder.Clear();

var token1 = RecordingServices.DefaultRecorder.AddRestorePoint("first");
order.Details.Add(new OrderDetail { Id = 1, Product = "apples", Quantity = 5, UnitPrice = 1.99M });

var token2 = RecordingServices.DefaultRecorder.AddRestorePoint("second");
order.Details.Add(new OrderDetail { Id = 2, Product = "potatoes", Quantity = 10, UnitPrice = .99M });

// Removes detail 2
RecordingServices.DefaultRecorder.UndoTo(token2);

// Removes detail 1
RecordingServices.DefaultRecorder.UndoTo(token1);

You have a great deal of control over the Recorder and other recordable features, and in general the implementation looks full-featured and quite useful. There’s also a series of blog posts on the PostSharp site with more detailed information.

The recordable feature is available in the pre-release version of the PostSharp Model Pattern Library.

Filed under: c# Tagged: AOP, PostSharp

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I started my professional career as a mainframe Assembler programmer at a small insurance company. Since then I've developed dozens of enterprise and commercial solutions using a grab bag of technologies. I've been developing with .NET since 2003 and still love it.

Comments and Discussions

 
SuggestionCasting to an interface introduced by PostSharp Pin
Gael Fraiteur18-May-14 7:13
Gael Fraiteur18-May-14 7:13 
SuggestionGetting threading models to work Pin
Gael Fraiteur17-May-14 23:14
Gael Fraiteur17-May-14 23:14 
Thank you for writing about PostSharp. I think the difficulties with the threading models may be caused by runtime model verification was turned off by default in previous builds. Counting from 3.2.20, it should be turned on in debug mode by default and turned off by default in release mode (depending on the value of the Optimize setting of the C#/VB compiler). It can be overwritten using the RuntimeVerificationEnabled property of the aspect.
GeneralRe: Getting threading models to work Pin
KimJohnson19-May-14 7:04
KimJohnson19-May-14 7:04 
GeneralRe: Getting threading models to work Pin
Gael Fraiteur19-May-14 19:07
Gael Fraiteur19-May-14 19:07 
GeneralRe: Getting threading models to work Pin
KimJohnson20-May-14 3:16
KimJohnson20-May-14 3:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.