Click here to Skip to main content
Click here to Skip to main content

Dynamic Visitor Pattern

By , 3 May 2013
Rate this:
Please Sign up or sign in to vote.

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 

 

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 

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.   

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 

    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  

 

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 

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.   

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 

    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

 

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)

About the Author

Sergey Morenko
Software Developer (Senior)
Russian Federation Russian Federation
B.Sc. in Computer Science.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinmemberPranay Rana3-Feb-14 1:45 
GeneralRe: My vote of 5 PinprofessionalSergey Morenko4-Feb-14 9:50 
GeneralRe: My vote of 5 PinmemberPranay Rana4-Feb-14 12:31 
GeneralMy vote of 4 PinprofessionalPaulo Zemek7-Jan-14 10:32 
GeneralRe: My vote of 4 PinprofessionalSergey Morenko7-Jan-14 10:49 
GeneralMy vote of 4 PinmemberJunRui3-May-13 19:19 
GeneralRe: My vote of 4 PinmemberGSerjo4-May-13 0:34 
QuestionNot an article PinmvpOriginalGriff3-May-13 2:03 
AnswerRe: Not an article PinmemberGSerjo3-May-13 2:29 
GeneralRe: Not an article PinmvpOriginalGriff3-May-13 3:39 
AnswerRe: Not an article PinmemberGSerjo3-May-13 6:18 
GeneralMy vote of 2 Pinmembercrood31-Mar-13 1:15 
GeneralRe: My vote of 2 PinmemberGSerjo31-Mar-13 6:29 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 3 May 2013
Article Copyright 2013 by Sergey Morenko
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid