.Org Code for the social web, data management, and other interesting things.

5Aug/105

Generic Visitor Pattern in C#

The traditional model for application of the Visitor pattern, assuming that the visitable classes are immutable, is that the visitor class can produce data in one of two ways: it can either return data from the Visit() method, or it can aggregate data types inside the class. (The third option is to do both, of course.)

All well and good, but this model feels pretty limiting if you want to have multiple visitors that work on the same objects but produce different data types. In that case you can a) create as many versions of the visitor interface as you have data types to be returned, b) always aggregate within the visitor class, thus having to deal with mutable state inside the visitor, or c) always return object. Of those options, the first is tedious and the third is ugly. To my mind, both of them defeat the purpose of having the pattern in the first place. The second option is not bad, but I wanted something better than that.

Modern languages such as Java and C# include support for generics, so I tried my hand at writing a generic Visitor pattern that could be used on the same data structures by multiple visitor implementations that produce different data types. Using the new (well, newly old) features in C# 3.0, I was able to get a lot more. Let's start by looking at the IVisitor interface, which is parameterized by data type:

IVisitor is fairly straightforward when you think about it — we define a set of methods that accept an object of type R and produce an object of type R by visiting an IVisitable. This way we can write a single IVisitor interface for a data structure and then specialize it for a specific type later on, during the implementation. This is a nice improvement, but there's more we can do. C# 3.5 introduced extension methods, which allow us to attach methods to a wide range of classes at one time. Using a single extension method, we can eliminate the need for the second half of the traditional Visitor pattern, the Accept() method:

Now the Accept() method is available to be called on any class that implements IVisitable, as long as we provide an implementation of IVisitor. This means that we don't have to copy and paste the same code to every class that implements IVisitable any more! We can just call this method and .NET will wire it up.

The above definition makes use of a marker interface called IVisitable, but you could even restrict the application implicitly by which Visit() methods you define. In the case of a marker interface, you simply need to include a default method for visiting an IVisitable like so:

Which completes the generic visitor. Using a generalized class like this one allows us to program in a more functional and (hopefully) testable way. Plus, it's nice to actually use the new language features once in a while.

It's entirely possible that I've missed something in the implementation, so be sure to shout out in the comments if you've found a problem. Otherwise, enjoy your new generic Visitor in C#!

P.S. I should also give credit to some StackOverflow answers that suggested this approach, as well as a blog post on generic visitors in C# by Nicolas Penin, though I'm not aware of anyone else with an identical approach.

Update: Daniel Gröndal pointed out some issues with this approach in the comments. It turns out that the extension method does not work as I thought it did during my initial testing. Please read the comment thread for more details!

Comments (5) Trackbacks (0)
  1. Hi!

    I fail to see how this actually solves the same problem as a Visitor pattern do. Could you provide a sample on how to use this? Extension methods are static and cannot therefore be virtual. So the compiler needs an exact type on compilation to resolve such a call. Using a Visitor pattern is useful when you have base class pointers pointing to a derived type that is not known at compile time.

    //daniel

  2. Hi Daniel!

    You raise a fair concern. I think it may be that I am not explaining the generics properly, but it very well could be that I am not understanding your comment properly and have made a mistake! I will put together an example and post it in the next day or two, as time allows.

    Philip

  3. Daniel,

    You are correct that a typical implementation of the Visitor pattern using this generic setup does not work without some finessing. In fact, if you try it as-is you will get a StackOverflowException. Unfortunately this way of writing a visitor requires extra checks in the visitor class itself. For instance, with model classes Building, Room, and Item:


    public string Visit(IVisitable visitable, string data)
    {
    if (visitable == null)
    return data;

    Building b = visitable as Building;
    if (b != null)
    return this.Visit(b, data);

    Room r = visitable as Room;
    if (r != null)
    return this.Visit(r, data);

    Item i = visitable as Item;
    if (i != null)
    return this.Visit(i, data);

    return null;
    }

    Note that the visitor already had knowledge of the specific classes involved, as it provides overloads for them. However, this is somewhat ugly. Furthermore, it means that we lose some of the static type checking afforded by the compiler — i.e., if I add another type, I have to remember to check it in the top-level visit method, and the compiler won’t complain if I don’t.

    Now that I’ve had the chance to evaluate this approach again, I think that it is better not to attempt to use fancy extension methods for this purpose. The first part of the post is still valid, I think.

  4. Hi!

    Sorry if I broke something :-) . I just could not get it.

    What you are doing in your last post is nothing but a “type switch” which is what I would use the Visitor pattern to avoid.

    //daniel

  5. Daniel,

    No problem. I did not catch this at first, as I was writing it as an exercise and did not have occasion to rewrite any visitor classes to use this type of pattern.

    Note that it is still useful to point out the generic parameterized visitor, though obviously that is not a new development. The same effect can be had by storing state in the visitor, but that does not let you program in as functional of a style.

    I would simply revise the post, but I feel like that would be dishonest. However, I will put a note at the bottom.


Leave a comment

(required)

No trackbacks yet.