Thursday, December 3, 2009

Using C# extension methods to extend plugin architectures

I've spent a lot of time recently writing a plugin for Autodesk Revit Architecture 2010. Extending Revit involves writing managed code that implements a special interface (Autodesk.Revit.IExternalApplication) that gets called by the host application when special events occur - the application starts up, one of your toolbar buttons was clicked, a document was opened / closed / saved etc.
Access to the host is provided as a parameter to the methods defined in the interface. One of the main objects of interest for the work I'm doing is the Autodesk.Revit.Document class. This is my window into Revit documents, providing information about elements in the document.
The catch: You cannot change how these objects behave, since they are provided by the host. If Autodesk.Revit.Document is missing a Rooms property that lists all the rooms in the document, well, you are out of luck.
The traditional object oriented solution to this problem is inheritance. You subclass the lacking class in question and add the desired functionality. This will not work in a plugin situation. You have no control of how Revit instantiates its document object.
You could decorate the object with new functionality: Implement the Document interface (in this case you would have to extract the interface first) and delegate all invocations to a stashed away instance received via Revit. This can fly, but its pretty ugly to do by hand - you might want to generate this.
C# 3.0 comes with a new solution to this problem: Extension Methods. Lets look at some code.
/// <summary>
/// Provides some utility methods for working with Revit.Document objects.
/// </summary>
public static class RevitDocumentExtensions
{
    /// <summary>
    /// Returns a list of all room objects in a Revit document.
    /// This excludes room
    /// </summary>
    public static IEnumerable<Room> GetRooms(this Document document)
    {
        var roomElements = new List<Element>();
        var filterFactory = document.Application.Create.Filter;
        var filter = filterFactory.NewTypeFilter(typeof(Room));
            document.get_Elements(filter, roomElements);

        // filter the rooms so only the placed ones are returned.
        return roomElements.Cast<Room>().Where(r => r.Location != null);
    }
}
Given an Autodesk.Revit.Document object, GetRooms can be called like so:
var rooms = RevitDocumentExtensions.GetRooms(document);
This is nothing new, hardly exciting, but works.
Did you notice the magic keyword this in the functions signature?
public static IEnumerable<Room> GetRooms(this Document document)
That's new. We are still talking static methods here, and that is all the .NET runtime ever sees, but the C# compiler will allow you to use this method syntactically as if it were defined in the Document class! So our usage chages to this:
var rooms = document.GetRooms();
Neat, huh? It looks as if we have subclassed Autodesk.Revit.Document and added a new instance method. Using this technique, a plugin API can be extended to better suit the needs of the plugin.
I will post more examples on how I used extension methods to augment Revit in my next post.

1 comment:

  1. Not quite sure if possible with .NET, but I'd love to see a shell for Autoit since you already are very familiar with it. It's a great, quick scripting tool that can handle a lot of common commands.

    ReplyDelete