Graft-on Visitor in C#

C# extension methods allow you to add new functions onto a class without modifying the original class. With the addition of the dynamic keyword you can effectively add new virtual functions to a class as well using what is very similar to the visitor pattern:

using System;
using System.Collections.Generic;
using System.Linq;
using visitor.Extension;

namespace visitor
{
    namespace Extension
    {
        static class Extension
        {
            public static void RunVisitor(this IThing thing, IThingOperation thingOperation)
            {
                thingOperation.Visit((dynamic)thing);
            }

            public static ITransformedThing GetTransformedThing(this IThing thing, int arg)
            {
                var x = new GetTransformedThing {Arg = arg};
                thing.RunVisitor(x);
                return x.Result;
            }
        }
    }

    interface IThingOperation
    {
        void Visit(IThing iThing);
        void Visit(AThing aThing);
        void Visit(BThing bThing);
        void Visit(CThing cThing);
        void Visit(DThing dThing);
    }

    interface ITransformedThing { }

    class ATransformedThing : ITransformedThing { public ATransformedThing(AThing aThing, int arg) { } }
    class BTransformedThing : ITransformedThing { public BTransformedThing(BThing bThing, int arg) { } }
    class CTransformedThing : ITransformedThing { public CTransformedThing(CThing cThing, int arg) { } }
    class DTransformedThing : ITransformedThing { public DTransformedThing(DThing dThing, int arg) { } }

    class GetTransformedThing : IThingOperation
    {
        public int Arg { get; set; }

        public ITransformedThing Result { get; private set; }

        public void Visit(IThing iThing) { Result = null; }
        public void Visit(AThing aThing) { Result = new ATransformedThing(aThing, Arg); }
        public void Visit(BThing bThing) { Result = new BTransformedThing(bThing, Arg); }
        public void Visit(CThing cThing) { Result = new CTransformedThing(cThing, Arg); }
        public void Visit(DThing dThing) { Result = new DTransformedThing(dThing, Arg); }
    }

    interface IThing {}
    class Thing : IThing {}
    class AThing : Thing {}
    class BThing : Thing {}
    class CThing : Thing {}
    class DThing : Thing {}
    class EThing : Thing { }

    class Program
    {
        static void Main(string[] args)
        {
            var things = new List<IThing> { new AThing(), new BThing(), new CThing(), new DThing(), new EThing() };
            var transformedThings = things.Select(thing => thing.GetTransformedThing(4)).Where(transformedThing => transformedThing != null).ToList();
            foreach (var transformedThing in transformedThings)
            {
                Console.WriteLine(transformedThing.GetType().ToString());
            }
        }
    }
}

Note, because someone can come along and add a new derived type you should handle the IThing case somehow. In the example above I return null and filter it out of the result set. For other cases it may make sense to return a generic form of the transformed thing, or to throw an exception. Once the IThingVisitor interface is updated with the new concrete type all the operations will be forced to decide how they deal with the new concrete type. This is the same decision all concrete types have to make for empty virtual functions when deriving from an abstract class.

Wondering why you wouldn't just add GetTransformedThing as a virtual method to the base class? The problem with adding virtuals methods for each new case is that you drag the dependencies for handling that case into the classes list of dependencies. After a while everything ends up depending on everything else and our separation of concerns starts to get blurry.

The visitor approach is ideal for dealing with cases where the object hierarchy has many consumers. Take for instance an abstract syntax tree. We might want to pretty print it, compile it to code, or run optimizations on it. Adding new virtuals to the base object and implementing them in the children would pollute the tree code with dependences for all of these functions and force anyone wanting to reuse the tree code to take those dependencies with it. By using the approach above we can leave the tree code alone, and thus in a more reusable state.