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 :
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), " "); } [...]
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 !