Click here to Skip to main content
15,881,281 members
Articles / Programming Languages / C# 4.0

Dynamic Visitor Pattern

Rate me:
Please Sign up or sign in to vote.
4.55/5 (18 votes)
3 May 2013CPOL2 min read 52.9K   217   30   17
Visitor design pattern - dynamic implementation.

Introduction   

Visitor “represent[s] an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates” (GoF).

I believe you know the Visitor design pattern, so here is dynamic implementation. The dynamic visitor separates the pattern from the business logic. The visitor pattern is based on double-dispatch, so we should implement runtime dispatching. We could do this one  for example thru dynamic or Type.

Following class hierarchy is used in all examples 

Image 1 

Visitor based on "Dynamic

The dynamic type is resolved at runtime. By specify concrete type to dynamic type we can dispatch to concrete action.   

The code consists of several classes:

  • Visitor - factory for visitor
  • IActionVisitor<in TBase> - visitor's interface 

IActionVisitor<in TBase>  -  lets you register Action<T> on concrete type 

C#
public interface IActionVisitor<in TBase>
        where TBase : class
{
    /// <summary>
    /// Register action on <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T">Concrete type.</typeparam>
    /// <param name="action">Action.</param>
    void Register<T>(Action<T> action)
        where T : TBase;

    /// <summary>
    /// Visit concrete type.
    /// </summary>
    /// <param name="value">Type to visit.</param>
    void Visit<T>(T value)
        where T : TBase;
}  

Visitor - factory for IActionVisitor<in TBase>  visitor.   

C#
public static class Visitor
{
    /// <summary>
    /// Create <see cref="IActionVisitor{TBase}"/>.
    /// </summary>
    /// <typeparam name="TBase">Base type.</typeparam>
    /// <returns>New instance of <see cref="IActionVisitor{TBase}"/>.</returns>
    public static IActionVisitor<TBase> For<TBase>()
        where TBase : class
    {
        return new ActionVisitor<TBase>();
    }

    private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
        where TBase : class
    {
        private readonly Dictionary<Type, dynamic> _repository =
            new Dictionary<Type, dynamic>();

        public void Visit<T>(T value)
            where T : TBase
        {
            dynamic action = _repository[value.GetType()];
            action((dynamic)value);
        }

        public void Register<T>(Action<T> action)
            where T : TBase
        {
            _repository[typeof(T)] = action;
        }
    }
}  

The magic happens here 

private readonly Dictionary<Type, dynamic> _repository = new Dictionary<Type, dynamic>(); 

and here 

public void Visit<T>(T value)
    where T : TBase
{
    dynamic action = _repository[value.GetType()];
    action((dynamic)value);
}

DLR (dynamic language runtime) resolves type and executes concrete action. 

Example 

C#
private static void DynamicActionVisitor()
{
    Stopwatch stopwatch = Stopwatch.StartNew();

    IActionVisitor<Letter> visitor = Visitor.For<Letter>();
    visitor.Register<A>(x => Console.WriteLine(x.GetType().Name));
    visitor.Register<B>(x => Console.WriteLine(x.GetType().Name));

    Letter a = new A();
    Letter b = new B();
    visitor.Visit(a);
    visitor.Visit(b);

    stopwatch.Stop();
    Console.WriteLine("Execution time: {0} ms", stopwatch.Elapsed.TotalMilliseconds);
}

Here is the result of running  

Image 2 

First call with runtime magic is too expensive, so the Visitor based on dynamic is useful only with many calls. 

Visitor based on "Type

By specify concrete type to Action<T> we can dispatch to concrete action.

The code consists of several classes:

  • Visitor - factory for visitor
  • IActionVisitor<in TBase> - visitor's interface 

IActionVisitor<in TBase>  -  lets you register Action<T> on concrete type 

C#
public interface IActionVisitor<in TBase>
        where TBase : class
{
    /// <summary>
    /// Register action on <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T">Concrete type.</typeparam>
    /// <param name="action">Action.</param>
    void Register<T>(Action<T> action)
        where T : TBase;

    /// <summary>
    /// Visit concrete type.
    /// </summary>
    /// <param name="value">Type to visit.</param>
    void Visit<T>(T value)
        where T : TBase;
}  

Visitor - factory for IActionVisitor<in TBase>  visitor.   

C#
public static class Visitor
{
    /// <summary>
    /// Create <see cref="IActionVisitor{TBase}"/>.
    /// </summary>
    /// <typeparam name="TBase">Base type.</typeparam>
    /// <returns>New instance of <see cref="IActionVisitor{TBase}"/>.</returns>
    public static IActionVisitor<TBase> For<TBase>()
        where TBase : class
    {
        return new ActionVisitor<TBase>();
    }

    private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
        where TBase : class
    {
        private readonly Dictionary<Type, Action<TBase>> _repository =
            new Dictionary<Type, Action<TBase>>();

        public void Register<T>(Action<T> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }

        public void Visit<T>(T value)
            where T : TBase

        {
            Action<TBase> action = _repository[value.GetType()];
            action(value);
        }
    }
}   

The magic is here, we create new Action<TBase> and do not lose the concrete type 

public void Register<T>(Action<T> action)
    where T : TBase
{
    _repository[typeof(T)] = x => action((T)x);
}

and here.  We use value.GetType()  for resolving object type in runtime 

 public void Visit<T>(T value)
    where T : TBase

{
    Action<TBase> action = _repository[value.GetType()];
    action(value);
}

Example 

C#
private static void DynamicActionVisitor()
{
    Stopwatch stopwatch = Stopwatch.StartNew();

    IActionVisitor<Letter> visitor = Visitor.For<Letter>();
    visitor.Register<A>(x => Console.WriteLine(x.GetType().Name));
    visitor.Register<B>(x => Console.WriteLine(x.GetType().Name));

    Letter a = new A();
    Letter b = new B();
    visitor.Visit(a);
    visitor.Visit(b);

    stopwatch.Stop();
    Console.WriteLine("Execution time: {0} ms", stopwatch.Elapsed.TotalMilliseconds);
}

Here is the result of running

Image 3 

Visitor based on "Type" - Full version  

IActionVisitor<in TBase>, IFuncVisitor<in TBase, TResult>   -  lets you register Action<T>, Func<T, TResult> on concrete type  

public interface IFuncVisitor<in TBase, TResult>
    where TBase : class
{
    /// <summary>
    /// Register action on <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T">Concrete type.</typeparam>
    /// <param name="action">Action.</param>
    void Register<T>(Func<T, TResult> action)
        where T : TBase;

    /// <summary>
    /// Visit concrete type.
    /// </summary>
    /// <param name="value">Type to visit.</param>
    /// <returns>Result value.</returns>
    TResult Visit<T>(T value)
        where T : TBase;
}
public interface IActionVisitor<in TBase>
    where TBase : class
{
    /// <summary>
    /// Register action on <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T">Concrete type.</typeparam>
    /// <param name="action">Action.</param>
    void Register<T>(Action<T> action)
        where T : TBase;

    /// <summary>
    /// Visit concrete type.
    /// </summary>
    /// <param name="value">Type to visit.</param>
    void Visit<T>(T value)
        where T : TBase;
}

Visitor - factory for IFuncVisitor<in TBase, TResult>, IActionVisitor<in TBase>  visitors. 

public static class Visitor
{
    public static IFuncVisitor<T, TResult> For<T, TResult>()
        where T : class
    {
        return new FuncVisitor<T, TResult>();
    }

    public static IActionVisitor<T> For<T>()
        where T : class
    {
        return new ActionVisitor<T>();
    }

    private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
        where TBase : class
    {
        private readonly Dictionary<Type, Action<TBase>> _repository =
            new Dictionary<Type, Action<TBase>>();

        public void Register<T>(Action<T> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }

        public void Visit<T>(T value)
            where T : TBase
        {
            Action<TBase> action = _repository[value.GetType()];
            action(value);
        }
    }

    private sealed class FuncVisitor<TBase, TResult> : IFuncVisitor<TBase, TResult>
        where TBase : class
    {
        private readonly Dictionary<Type, Func<TBase, TResult>> _repository =
            new Dictionary<Type, Func<TBase, TResult>>();

        public void Register<T>(Func<T, TResult> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }

        public TResult Visit<T>(T value)
            where T : TBase
        {
            Func<TBase, TResult> action = _repository[value.GetType()];
            return action(value);
        }
    }
}

Conclusion 

That's all for now, I hope you enjoyed it, please take the time to post a comment. Thanks for reading the article. 

History 

  • 17th March, 2013: Initial version.
  • 03d  May, 2013: Added Visitor based on Type 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
B.Sc. in Computer Science.

Comments and Discussions

 
GeneralMy vote of 5 Pin
D V L2-Sep-15 18:57
professionalD V L2-Sep-15 18:57 
GeneralRe: My vote of 5 Pin
Sergey Morenko2-Sep-15 23:35
professionalSergey Morenko2-Sep-15 23:35 
Generalnice Pin
joyhen12312312-Jul-15 16:01
joyhen12312312-Jul-15 16:01 
GeneralRe: nice Pin
Sergey Morenko13-Jul-15 13:43
professionalSergey Morenko13-Jul-15 13:43 
GeneralMy vote of 5 Pin
Pranay Rana3-Feb-14 1:45
professionalPranay Rana3-Feb-14 1:45 
GeneralRe: My vote of 5 Pin
Sergey Morenko4-Feb-14 9:50
professionalSergey Morenko4-Feb-14 9:50 
GeneralRe: My vote of 5 Pin
Pranay Rana4-Feb-14 12:31
professionalPranay Rana4-Feb-14 12:31 
GeneralMy vote of 4 Pin
Paulo Zemek7-Jan-14 10:32
mvaPaulo Zemek7-Jan-14 10:32 
GeneralRe: My vote of 4 Pin
Sergey Morenko7-Jan-14 10:49
professionalSergey Morenko7-Jan-14 10:49 
GeneralMy vote of 4 Pin
JRoger_3-May-13 19:19
JRoger_3-May-13 19:19 
GeneralRe: My vote of 4 Pin
Sergey Morenko4-May-13 0:34
professionalSergey Morenko4-May-13 0:34 
QuestionNot an article Pin
OriginalGriff3-May-13 2:03
mveOriginalGriff3-May-13 2:03 
AnswerRe: Not an article Pin
Sergey Morenko3-May-13 2:29
professionalSergey Morenko3-May-13 2:29 
GeneralRe: Not an article Pin
OriginalGriff3-May-13 3:39
mveOriginalGriff3-May-13 3:39 
AnswerRe: Not an article Pin
Sergey Morenko3-May-13 6:18
professionalSergey Morenko3-May-13 6:18 
GeneralMy vote of 2 Pin
crood31-Mar-13 1:15
crood31-Mar-13 1:15 
GeneralRe: My vote of 2 Pin
Sergey Morenko31-Mar-13 6:29
professionalSergey Morenko31-Mar-13 6:29 
Code should be self-documenting, take a look "Refactoring: Improving the Design of Existing Code". I'll add more comments

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.