Thursday, 19 March 2009

Visualizing Expressions in PropertyGrid

The problem

The Expression API is complex; not least because picking apart an Expression is complicated. For example, lets pick an expression that removes the first and last characters from a string:

s => s.Length > 2 ? s.Substring(1, s.Length - 2) : s;

If you are working regularly with Expression, you often need to be able to visualize it (if only to stay sane); this counts doubly if we want to understand how to construct our own Expression to do something similar. Ideally without needing any external tools / plug-ins, and something a bit more permanent than the inspector in the IDE.

If you wanted to write down what that actually means (which is what Expression has to do):

  • given a parameter "s" 
    • branch by evaluating the greater-than operator with arguments: 
      • query the Length of "s"
      • the constant 2
    • if true, evaluate Substring on the parameter "s" with arguments: 
      • the constant 1
      • the result of the subtraction operator with arguments:
        • query the Length of "s"
        • the constant 2
    • if false, evaluate the parameter "s"

Although fairly mechanical, pulling apart Expressions can take time. If only the system could display it for us.

The naïve approach

But wait a minute! PropertyGrid is good at displaying tree-like data - lets try setting the SelectedObject to an instance of the above Expression:

The PropertyGrid control, showing minimal data of the outermost (lambda) Expression only.

Disappointed? It doesn't really help us (and note that the Parameters collection is not expandable). The problem is that PropertyGrid uses TypeDescriptor (and friends) to query the metadata, and Expression simply isn't configured to allow hierarchical browsing. But what many people don't realise is that TypeDescriptor allows runtime configuration.

Changing the type-converter

Behind the scenes, PropertyGrid uses the TypeConverter associated with a type (or an individual member), by querying GetPropertiesSupported(), and (if it returns true) GetProperties(). And even better, there is an inbuilt converter (ExpandableObjectConverter) that exposes all the public members for nested browsing.

Additionally, TypeDescriptor offers an indirect way to associate a different type-converter with a type; by adding an attribute at runtime*. Since Expression is the base class for all the interesting objects in the Expression API, we only have to tweak this one type:

  TypeDescriptor.AddAttributes(typeof(Expression),
new TypeConverterAttribute(typeof(ExpandableObjectConverter)));

This is broadly equivalent to using the attribute at compile-time:

  [TypeDescriptor(typeof(ExpandableObjectConverter))]
public abstract class Expression { /* ... */ }

*=for the pedants (myself included), I should note that we only add an attribute to the runtime model (System.ComponentModel); reflection is unaware of this change.

What does it look like now?

So how much difference does that one line make? Test again, and we see something very different - both in the main grid, and in the collection pop-ups:

The PropertyGrid control, showing the full hierarchy of nodes that describe the Expression. The Expression Collection Editor, showing the members and their hierarchical composition.

So with just 1 line of code, we've obtained a free tool for exploring Expression; I'm not saying it is the prettiest tool ever, but it is plenty good enough, considering that the main audience would be you, the developer.

It begs the question; why isn't it expandable in the first place?