Freigeben über


Implementing the Visitor pattern with the 'dynamic' feature of C# 4.0

A lot of the talk around the new dynamic keyword in C# 4.0 (available in the beta of Visual Studio 2010) is around interaction with external components (dynamic languages, HTML DOM, COM) but I'm most excited about being able to stragetically add dynamic lookup to an ordinary C# program. Normally, when an object is passed to a method the method lookup depends on the compile-time type of the object. This means that code like this:

class MyClass
{
 public void Foo(Stream s) { Console.WriteLine("Stream"); }
 public void Foo(MemoryStream s) { Console.WriteLine("MemoryStream"); }
}

class Program
{
 static void DoSomething(Stream s)
 {
  MyClass obj = new MyClass();
  obj.Foo(s);
 }

 static void Main(string[] args)
 {
  DoSomething(new MemoryStream());

 }

Will print out 'Stream', not 'MemoryStream'. Method resolution for dynamic objects has to use the runtime type of the object, so simply by changing DoSomething() to take a dynamic instead of a Stream we get a program that prints out 'MemoryStream'. Basically, dynamic lets us implement multi-methods in C#! (Which I really do find very exciting).

A good use of this feature is when implementing the Visitor pattern. C# code that visits all the nodes in a Linq expression tree tends to be quite ugly, with a huge switch statement and a lot of casts to traverse the tree. By casting the Expression class to dynamic we can call a Visit() method which depends on the exact subclass of Expression. The code below prints an expression tree without needing any explicit conditional or loop statements (using dynamic makes the runtime do all the boring lookup work for us). It is also quite a bit shorter than the C# 3.0 equivalent. Note that if there isn't an implementation for a specific expression type then the Visit method for the parent class will be called.

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq.Expressions;

namespace MultiMethods
{
    class ExpressionPrinter
    {
        private readonly IndentedTextWriter output;

        public ExpressionPrinter(TextWriter output)
        {
            this.output = new IndentedTextWriter(output);
        }

        public void Print(Expression exp)
        {
            dynamic d = exp;
            this.Visit(d);
        }

        private void Visit(Expression exp)
        {
            output.WriteLine("Expression");
        }

        private void Visit(BinaryExpression exp)
        {
            output.WriteLine("BinaryExpression: {0}", exp.NodeType);
            this.VisitSubExpression(exp.Left);
            this.VisitSubExpression(exp.Right);
        }

        private void Visit(ConditionalExpression exp)
        {
            output.WriteLine("ConditionalExpression");
            this.VisitSubExpression(exp.Test);
            this.VisitSubExpression(exp.IfTrue);
            this.VisitSubExpression(exp.IfFalse);
        }

        private void Visit(ConstantExpression exp)
        {
            output.WriteLine("ConstantExpression: {0}", exp.Value);
        }

        private void Visit(MethodCallExpression exp)
        {
            output.WriteLine("MethodCall: {0}", exp.Method.Name);
        }

        private void Visit(ParameterExpression exp)
        {
            output.WriteLine("ParameterExpression: {0}", exp.Name);
        }

        public void Visit(UnaryExpression exp)
        {
            output.WriteLine("UnaryExpression: {0}", exp.NodeType);
            this.VisitSubExpression(exp.Operand);
        }

        private void VisitSubExpression(Expression exp)
        {
            output.Indent++;
            dynamic d = exp;
            this.Visit(d);
            output.Indent--;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var p = new ExpressionPrinter(Console.Out);
            Expression<Func<int, int>> e = i => (0 == i%2) ? -i * i : Math.Abs(i);
            p.Print(e.Body);
        }
    }
}

The output is:

ConditionalExpression
    BinaryExpression: Equal
        ConstantExpression: 0
        BinaryExpression: Modulo
            ParameterExpression: i
            ConstantExpression: 2
    BinaryExpression: Multiply
        UnaryExpression: Negate
            ParameterExpression: i
        ParameterExpression: i
    MethodCall: Abs

Comments

  • Anonymous
    May 29, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout