Improving the toolbox : building our own expression tree visualizations
In the last two posts, I included sample visualizations of expression trees. I’ve built these using a visitor pattern, that produces HTML markup. This is a good example of the use of this pattern, so I’ll try to explain how it allows us to work with expression trees.
The expression tree visualization that I build is based on HTML / CSS3, using the code provided at http://thecodeplayer.com/walkthrough/css3-family-tree. Basically, the markup is formed of unordered lists (“ul / li” tags) that contains nested lists. You can view the generated HTML source by inspecting the DOM in the previous post.
The visualization builder is a class called TreeVisualizer, inheriting from ExpressionVisitor :
<p> internal class TreeVisualizer : ExpressionVisitor { private StringBuilder builder; private HtmlTextWriter writer; private TreeVisualizer() { this .builder = new StringBuilder(); this .writer = new HtmlTextWriter( new StringWriter( this .builder, CultureInfo.InvariantCulture), " " ); } [...]</p> |
The constructor is private, the only way to use this class is through a static method called BuildVisualization :
internal static string BuildVisualization(Expression expression) { TreeVisualizer visualizer = new TreeVisualizer(); visualizer.Visit(expression); return visualizer.Visualization; } |
The returned property, Visualization, is defined this way :
public string Visualization { get { return this .builder.ToString(); } } |
The class uses also two utility methods :
- GetSimplifiedType, which gives a user-friendly string representation of a given type.
private string GetSimplifiedType(Type type) { if (!type.IsGenericType) return type.Name; string genericName = type.Name.Split( '`' ).First(); string genericArguments = string .Join( ", " , type.GenericTypeArguments.Select( t => GetSimplifiedType(t))); return string .Format( "{0}<{1}>" , genericName, genericArguments); } |
- VisitAndBuildTree, which adds content to the HtmlTextWriter based on the tree nodes that it visits :
private Expression VisitAndBuildTree( string nodeName, string nodeType, string nodeDescription, Func<Expression> childrenVisitorFunction = null ) { this .writer.RenderBeginTag( "li" ); this .writer.WriteLine(); this .writer.Indent++; this .writer.AddAttribute( "href" , "#" ); this .writer.RenderBeginTag( "a" ); this .writer.AddAttribute( "class" , "node-name" ); this .writer.RenderBeginTag( "span" ); this .writer.WriteEncodedText(nodeName); this .writer.RenderEndTag(); this .writer.WriteBreak(); if (! string .IsNullOrEmpty(nodeType)) { this .writer.AddAttribute( "class" , "node-type" ); this .writer.RenderBeginTag( "span" ); this .writer.WriteEncodedText(nodeType); this .writer.RenderEndTag(); this .writer.WriteBreak(); } this .writer.WriteEncodedText(nodeDescription); this .writer.RenderEndTag(); this .writer.WriteLine(); Expression baseReturn = null ; if (childrenVisitorFunction != null ) { this .writer.RenderBeginTag( "ul" ); this .writer.WriteLine(); this .writer.Indent++; baseReturn = childrenVisitorFunction(); this .writer.Indent--; this .writer.RenderEndTag(); this .writer.WriteLine(); } this .writer.Indent--; this .writer.RenderEndTag(); this .writer.WriteLine(); return baseReturn; } |
With the two previous functions, we can now override each VisitSomeTypeOfNode method and insert the appropriate details in the HTML. For instance :
protected override Expression VisitBinary( BinaryExpression node) { return VisitAndBuildTree( "Binary" , string .Empty, node.NodeType.ToString(), () => base .VisitBinary(node)); } |
protected override Expression VisitLambda<T>( Expression<T> node) { return VisitAndBuildTree( "Lambda" , GetSimplifiedType(node.Type), node.ToString(), () => base .VisitLambda<T>(node)); } |
protected override Expression VisitMethodCall( MethodCallExpression node) { return VisitAndBuildTree( "Call" , GetSimplifiedType(node.Type), node.Method.Name, () => base .VisitMethodCall(node)); } |
There are many methods, I’m not going to show them all here. I’ll just finish with a special one, VisitConstant. For this one, I just handled two special cases, when the constant is an IEnumerable, or when it is a string. This allows us to see the constants values for simple types in the tree, and a simplified type description if it is a generic IEnumerable.
protected override Expression VisitConstant( ConstantExpression node) { string type = GetSimplifiedType(node.Type); string value; if (node.Type.IsGenericType && node.Type.FindInterfaces( (t, o) => t.Name.StartsWith( "IEnumerable" ), true ).Any()) { value = type; } else if (type == "String" ) { value = string .Concat( "\"" , (( string )node.Value).Replace( "\"" , "\\\"" ), "\"" ); } else { value = node.Value.ToString(); } VisitAndBuildTree( "Constant" , type, value); return base .VisitConstant(node); } |
That’s all for tonight !