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
{
void Register<T>(Action<T> action)
where T : TBase;
void Visit<T>(T value)
where T : TBase;
}
Visitor
- factory for IActionVisitor<in TBase>
visitor.
public static class Visitor
{
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 visitorIActionVisitor<in TBase>
- visitor's interface
IActionVisitor<in TBase>
- lets you register Action<T>
on concrete type
public interface IActionVisitor<in TBase>
where TBase : class
{
void Register<T>(Action<T> action)
where T : TBase;
void Visit<T>(T value)
where T : TBase;
}
Visitor
- factory for IActionVisitor<in TBase>
visitor.
public static class Visitor
{
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
{
void Register<T>(Func<T, TResult> action)
where T : TBase;
TResult Visit<T>(T value)
where T : TBase;
}
public interface IActionVisitor<in TBase>
where TBase : class
{
void Register<T>(Action<T> action)
where T : TBase;
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