Archive

We live in a paradoxical world: all office applications support undo/redo, but very few business applications include this feature. Is this because undo/redo seems so difficult to implement? This is a problem we decided to address in PostSharp 4.0 with the Recordable pattern.

This article is part of a series of 5 about undo/redo:

  1. Announcement and introduction
  2. Getting started – tutorial
  3. Logical operations, scopes, naming
  4. Recorders, recorder providers, callbacks
  5. Case study: Visual Designer

NOTE: This blog post is about an available pre-release of PostSharp. You can install PostSharp 4.0 only using NuGet by enabling the “Include pre-release” option. Undo/Redo is implemented in the package PostSharp.Patterns.Model.

Recordable Pattern

There are several approaches to the undo/redo problem. The design patterns literature recommends the Memento pattern, which basically relies on a snapshotting mechanism: you take a snapshot before an operation, and restore the object model to that snapshot if the operation needs to be undone. An alternative approach is to record changes to the object model and to undo/redo these changes. This is the approach we chose.

The two principal parts of the Recordable pattern is the [Recordable] aspect, which injects into an class the ability to record changes, and the Recorder class, which stores these changes and expose the undo/redo semantics.

Let’s show how this works on a simple example:

[Recordable]
class TableBooking
{
    public string CustomerName { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
    public int NumberOfPersons { get; set; }
    public string TableId { get; set; }

    public void Postpone(TimeSpan time)
    {
        this.StartTime += time;
        this.EndTime += time;
    }
}

The [Recordable] custom attribute causes changes in all TableBooking objects to be recorded. These objects can be bound directly to the UI using XAML bindings or can be modified using C#.

By default, all changes are stored in a global recorder available from the RecordingServices.DefaultRecorder property. This property is provided for convenience; there are more options to assign different recorders if you need more flexibility. We’ll cover these advanced feature in later posts or in our final documentation.

As you would expect, the Recorder class gives you the methods Undo and Redo:

booking.NumberOfPersons = 2;
booking.NumberOfPersons = 4;
RecordingServices.DefaultRecorder.Undo();
// At this point, booking.NumberOfPersons == 2;
RecordingServices.DefaultRecorder.Redo();
// At this point, booking.NumberOfPersons == 4;

How it works

Under the cover, our implementation of the Recordable pattern records changes in all fields of the target class. When calling the Undo or Redo methods, fields are simply set to their previous value. This has the effect to restore the object to their previous state. Note that you can use the [NotRecorded] attribute to prevent a field or automatic property to be recorded.

Taking this approach naively could cause some issues with atomicity. Consider the Postpone method in the code above. This method changes two properties in such a way that the total duration of the table booking is unaffected. Now suppose that the user hits the Undo button just after having postponed the booking. We don’t want the undo operation to affect just EndTime but not StartTime. The user would expect all operations performed by the Postpone method to be undone.

PostSharp solves this problem by introducing the concept of recording scope. By default, all public methods of an object define an atomic scope. That is, all changes performed by the Postpone method are going to be undone atomically. This default behavior can be overridden by the [RecordingScope] attribute, which I will cover in a future article.

Explicit scopes

There are situations where you will want to programmatically control the recording scope. Suppose for instance that you bind the NumberOfPersons property to a slider control. While the user moves the slider, the value of the NumberOfPersons property is subsequently set to all values ranging from 1 to 10 and then back to 5, as the user releases the mouse button. While the user perceives this as a single operation, it actually results in 15 operations in the undo/redo list. This unpleasant experience can be improved by defining the recording scope programmatically, using the OpenScope method of the Recorder class.

The following code snippet shows how to define a recording scope that matches a scope of source code.

using (RecordingServices.DefaultRecorder.OpenScope())
{
    booking.NumberOfPersons = 4;
    booking.NumberOfPersons = 5;
    booking.NumberOfPersons = 6;
    booking.NumberOfPersons = 7;
}

To open a scope when the user starts manipulating a Slider and close the scope when the user stops, we would need to call OpenScope from the GotFocus event and call Dispose from LostFocus.

Object Graphs and Collections

Up to now, we’ve only tracked changes to fields of an intrinsic type. But what if the fields are of more complex types, such as other objects and collections? The solution is simple: all classes that need to be involved in an undo/redo operation need to be made [Recordable]. Of course, we would not want to ask you to develop your own recordable collections. We don’t even want to have specific collection classes that implement the Recordable behavior.

Do you remember our Aggregatable pattern? We faced the problem of providing collections that implement the parent-child relationship. Instead of providing specific classes, we develop collections to which the Aggregatable behavior can be dynamically injected. We’ve done exactly the same with Recordable. Our Recordable pattern relies on the Aggregatable patterns. All children collections will be automatically made recordable.

[Recordable]
public class Invoice
{
    [Child]
    public readonly AdvisableCollection<InvoiceLine> Lines = new AdvisableCollection<InvoiceLine>();

    [Child]
    public readonly AdvisableCollection<InvoiceDiscount> Discounts = new AdvisableCollection<InvoiceDiscount>();

    [Reference]
    public Customer Customer;
}

[Recordable]
public class InvoiceLine
{
    public decimal Quantity;

    [Reference]
    public Product Product;

    [Parent]
    public Invoice ParentInvoice { get; private set; }
}


[Recordable]
public class Customer
{
    public string Name { get; set; }
}

[Recordable]
public class InvoiceDiscount
{
    public decimal Percent;
    public string Reason;

    [Parent]
    public Invoice ParentInvoice { get; private set; }
}

Other features

In this article, I just wanted to introduce the basic features of our Recordable pattern implementation. There are many more features I will describe in a later post:

  • Multiple recorders: You can have several recorders in your application instead of the default single global one.

  • Callback methods that make your objects aware of undo/redo operations.        

  • Fully customizable operation naming so you can display the list of undoable operations to the user.

  • History trimming.

Summary

PostSharp 3.2 makes it much easier to implement the undo/redo in your Windows or Windows Phone application. You can try it yourself today by downloading the PostSharp.Patterns.Model pre-release package from NuGet and playing with the [Recordable] attribute and the Recorder class.

What do you think? Would you have implemented your current project differently if this feature was available? How much code would you have saved?

Happy PostSharping!

-gael

UPDATE: Change product version from PostSharp 3.2 to PostSharp 4.0.

NOTE: This blog post is about an available pre-release of PostSharp. You can install PostSharp 4.0 only using NuGet by enabling the “Include pre-release” option.

At PostSharp Technologies, we see our role as making it easier to build good software. We believe that source code, and not UML diagrams or documentation, should be the reference artifact. If the goal is to make it easier to build good software, then we should necessarily focus on making source code simpler. Ideally, the source code should be at the same level of abstraction as our thinking. So if we are thinking in terms of design patterns, we should be able to code in terms of design patterns. Unfortunately, conventional compilers have no concept of design patterns. This is why we built PostSharp: to empower developers to tell their compiler what needs to be done, not how to do it.

In PostSharp 4.0, we decided to implement more design patterns and to do it better. After some reflection, it became clear that many design patterns rely on a fundamental concept: the parent-child relationship. This is so common that we easily forget about it. Yet how many times did you find yourself implementing a parent-child relationship manually?

To move forward with other design patterns, we had to provide a good implementation of the parent-child relationship. We decided to stick to the UML terminology, where this kind of relationship is named an Aggregation. Objects in an aggregation relationship form a tree (acyclic graph). UML also defines a kind of relationship named a Composition. A composition relationship is a parent-child (or whole-part) relationship where the lifetime of the children is controlled by the parent; when the parent is destroyed, all children must be destroyed in cascade. Aggregation and composition are often considered equivalent in garbage-collected platforms because there is no explicit lifetime control. In .NET, the Composition pattern makes sense in combination with the Disposable pattern, i.e. children are disposed when the parent is disposed.

How did we do it in PostSharp 4.0?

Aggregatable Aspect

The core of our implementation of the Aggregation pattern is the Aggregatable aspect. Any class whose instances may need to be a part of an aggregation must be annotated with the [Aggregatable] custom attribute. You will find this aspect in the PostSharp.Patterns.Common library and the NuGet package.

Let’s examine this aspect in action on a simple example where an invoice entity is composed of one instance of the Invoice class and several instances of the InvoiceLine class:

[Aggregatable]
public class Invoice
{
    [Child]
    public readonly AdvisableCollection<InvoiceLine> Lines = new AdvisableCollection<InvoiceLine>();

    [Child]
    public readonly AdvisableCollection<InvoiceDiscount> Discounts = new AdvisableCollection<InvoiceDiscount>();

    [Reference]
    public Customer Customer;
}

[Aggregatable]
public class InvoiceLine
{
    [Reference]
    public Product Product;

    [Parent]
    public Invoice ParentInvoice { get; private set; }
}

[Aggregatable]
public class InvoiceDiscount
{
    public decimal Percent;
    public string Reason;

    [Parent]
    public Invoice ParentInvoice { get; private set; }
}

As shown, fields and automatic properties are annotated with the following custom attributes:

  • [Child] means that the field/property value is a child of the current instance.
  • [Parent] means that the field/property must be automatically set to the parent whenever the current instance is assigned to a [Child] field/property of another object.
  • [Reference] means that the value is neither a [Child] neither a [Parent].

Value-type or primitive-type fields and properties don’t need to be annotated. Note that it is allowed to assign a non-aggregatable object to a child field; the child object will be visible to the parent anyway.

IAggregatable interface

At this point, you may wonder 1) why we need an aspect to maintain the child-parent relationship, and 2) what is the aspect’s benefit over plain C# code.

The real motivation of the Aggregatable aspect is to expose the parent-child relationship as an abstraction that can be consumed by other components that don’t have knowledge of the shape of the domain objects.

The aspect makes this possible by automatically implementing the IAggregatable interface as below:

public interface IAggregatable 
{
    bool VisitChildren(ChildVisitor visitor, ChildVisitorOptions options);
    object Parent { get; }
    Relationship ParentRelationship { get; }
    event EventHandler ParentChanged;
    event EventHandler AncestorChanged;

    // Implementation details omitted.
}

The VisitChildren method allows any consumer to execute a delegate for each child of the object without knowing where these children are stored. In our Invoice example, children are stored both in the Lines and Discounts collection. It may seem weird to present children using a visitor rather than an enumerable, and we chose this design intentionally for performance reasons because the pattern can then be implemented without resorting to additional data structures.

AdvisableCollection and AdvisableDictionary

In the above example, you can see that the type of the Lines and Discounts fields of the Invoice class is special: AdvisableCollection. We clearly needed a special collection that would implement the Aggregatable pattern: minimally, we need a collection to set the Parent field of all children objects.

We could create an AggregatableCollection for the Aggegatable pattern, then a FreezableCollection for the Freezable pattern, then a ReaderWriterSynchronizedCollection, and so on. Every time you want to add a behavior to an object model, there is a need for a specialized collection type. This situation would quickly become unmanageable.

Instead, we decided to create a general collection to which behaviors can be added dynamically at runtime. An AdvisableCollection or an AdvisableCollection automatically implements the Aggregatable pattern as soon as it is assigned to a [Child] field of an Aggregatable object. This may sound a bit strange but this is plain old object-oriented programming, and no MSIL transformation is involved in this feature.

Since part of the Aggregatable pattern is to implement the IAggregatable interface, we needed a way to dynamically add interfaces to an object. This is clearly impossible in .NET, so instead we chose not to reinvent the wheel and borrow from COM’s QueryInterface method.

So, when you need to cast an AdvisableCollection to IAggregatable, you can use:

IAggregatable aggregatable = collection.QueryInterface<IAggregatable>();

Disposable Aspect

A composition relationship is, by definition, an aggregation relationship where the parent controls the lifetime of children. In .NET terms, it means that the Dispose method of a parent should invoke the Dispose method of children.

This is exactly what we did with the Disposable aspect. Based on the Aggregatable pattern, the Disposable pattern automatically implements the IDisposable interface, calling the Dispose method of all children in a depth-first traversal.

The Disposable aspect automatically adds the Aggregatable aspect if it is not yet applied to the type. Note that the leaves of the object tree don’t need to have the Disposable aspect if they already implement the IDisposable interface.

The following example demonstrates a composition of three classes: Multiplexer, Sink and TextWriter.

[Disposable]
class Multiplexer
{
    [Child]
    AdvisableCollection<Sink> sinks = new AdvisableCollection<Sink>();

    public void WriteLine(string text)
    {
        foreach (Sink sink in sinks)
        {
            sink.WriteLine(text);
        }
    }
    
}

[Disposable]
class Sink
{
    [Child]
    private TextWriter textWriter;
    
    public Sink(TextWriter textWriter)
    {
        this.textWriter = textWriter;
    }

    public void WriteLine(string text)
    {
        this.textWriter.WriteLine(text);
    }
}

In the example above, all TextWriter instances will be disposed when the parent Multiplexer instance will be disposed.

Advantage

PostSharp 4.0 now provides a ready-made implementation of two fundamental object relationship patterns: Aggregation and Composition. This lays the ground for other pattern implementations such as freezable, immutable, recordable and synchronized. The curious reader will find some of these patterns.
We’ll blog about these other patterns in the next days.

UPDATE: Change product version from PostSharp 3.2 to PostSharp 4.0.

As I look back at 2013, I am reminded of how much pain it sometimes takes to make a difference in the world, but how immense is the reward when what you believe in slowly comes true.

A year ago we were already 6 months late on the Microsoft release cycle. This year we added support for Visual Studio 2013 and .NET 4.5.1 the next day after the preview release and we soon caught up with the C# compiler.

A year ago, I almost lost faith in aspect-oriented programming. Today I strongly believe in a better, fundamentally simpler way to think about software. I believe a once-in-a-generation paradigm shift is coming. It will be an era in which developers will tell compilers what to do and not how to do it. In this vision, aspect-oriented programming plays a central goal in concert with static analysis and meta-programming. Today, we are proud as we contribute to the evolution of programming languages.

A year ago the company was in crisis. Today the team is solid and confident. This is why now my looking back is filled with gratitude and thanks for many.

I give thanks to our customers, those small teams of engineers who fight hard within their corporate bureaucracies to be allowed to use non-mainstream technologies.

I give thanks to our local team, who chose the shivers on the Santa Maria across unknown waters over the safety of cabotage in familiar territory.

I give special thanks to Marek Byszewski’s team for its crucial support during the storm, and to Art of Change’s Vlasta and Jana for their help in strengthening the team and our minds.

Last but not least, I give thanks to the Friends of PostSharp, those benevolent speakers who dedicated their free time to educate the community. Together, the Friends of PostSharp reached an audience of over 1,000 just this year.

We were looking for a way to express our gratitude and we thought there would be no better way than a donation to a charity on behalf of our community. We wanted to support a worthwhile cause that resonates with our industry. Thankfully, we were able to find a great one.

In the name of the Friends of PostSharp, we have donated $2,000 to the Czech Association Helping People with Autism (APLA).

Autism diagnoses have surged in the last few years and associations like APLA struggle to meet the demands of parents. Causes of the disease are still being debated. But beyond the usual jokes about socially-odd programmers, researchers have found signs that autism may indeed be more prevalent among children of those in highly intellectual and technical professions. Autism is a focal concern of industrialized societies and especially of high-tech professions.

Last for Matt, Donald, Dustin, Lance, Dror, Adam, Yan, Joe and Chad, thank you so much for your dedication and support.

We wish you all at least a few work-free days without PostSharp before the 2014 New Year.

Happy Christmas from all of us at PostSharp Technologies!

-gael