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

A tool for making C# visitors

, 13 Dec 2008
Rate this:
Please Sign up or sign in to vote.
A small VS add-in to implement a Visitor pattern.

Introduction

In my previous article, I showed how to auto-generate a decorator over one or several classes. In this article, I’ll show a more difficult generation technique: auto-generation of a Visitor pattern via creation of a visitor class and injection of visit methods into existing classes.

To try out the add-in, download the Zip with the binaries and extract them into your Addins folder.

Some Theory

If I wanted to translate several different types of objects into C#, I would typically give each of them a method similar to the following:

class MyEntity {
  // just an example!
  public something State { get; set; }

  // turn this object into c#
  public void ToCode(StringBuilder sb) 
  {
    sb.Append("static class " + State + " {}");
  }
  // other things here
}

The above would take the State property of the object being traversed (or visited), and will output valid C#. It looks simple enough, and you can litter your code with these types of statements. The only problem is when you want to output some other language, e.g., Nemerle. In this case, you need to refactor the ToCode() method to take a second parameter – the language to generate. This results in the following:

public void ToCode(StringBuilder sb, Language language)
{
  switch (language) {
    case Language.CSharp:
      sb.Append("static class " + State + " {}");
      break;
    case Language.Nemerle:
      sb.Append("module " + State + " {}");
      break;
  }
}

Making this change in lots of classes is unrealistic. In a typical hierarchy, you might have dozens, or even hundreds of locations where you’d need to change this code, and it’s very inconvenient. So, what do you do? Well, one way of doing it is, instead of using a StringBuilder, use an inheritable class (a decorator over StringBuilder). But then, statements for emitting the code cannot be done in the visited classes because, after all, a visited class no longer knows (or cares) whether C# or Nemerle code is required. Here’s what I mean:

public void ToCode(CodeBuilder cb)
{
  cb.VisitMyEntity(this);
}

Instead of doing anything in the visited method, we do an inversion of control, and code assembly happens in the builder’s method. This way, a C# builder would append the line in its own way, and the Nemerle builder otherwise. Congratulations – you’ve just seen an example of a full-fledged Visitor pattern.

An Example

So, what’s the problem? Well, the problem is that hierarchies are sometimes quite large, and making all this code by hand (especially if you never planned classes to be visitable) is unrealistic. For a hierarchy of 20 classes, you would require to create a visitor with 20 VisitXXX methods, and add 20 methods to each of the visited classes. By hand. Not an easy proposition.

My tool does it for you. Here’s how. First, right-click the project and choose Add|Visitor:

1.jpg

Then, give the visitor a name and select all the classes you want to visit:

2.jpg

Now, open the Method tab to customize your visit method. Here you can define:

  • Your method name
  • Any additional parameters for the method
  • Code that will get written immediately before calling the Visitor-owned method
  • Code that will get written immediately after calling the Visitor-owned method

3.jpg

After you press OK, two things happen. First, you get a generated visitor class similar to the following:

class MySpecialVisitor
{
  /// <summary>
  /// Makes a visit to <see cref="MyEntity"/>.
  /// </summary>
  /// <param name="target">The element to visit.</param>
  public void VisitMyEntity(MyEntity target)
  {
    // todo: visit MyEntity
  }
  /// <summary>
  /// Makes a visit to <see cref="OtherEntity"/>.
  /// </summary>
  /// <param name="target">The element to visit.</param>
  public void VisitOtherEntity(OtherEntity target)
  {
    // todo: visit OtherEntity
  }
}

See how the class accepts every type of object you selected as a parameter? It’s up to you to write actual handling code here. Now, in addition to this, every class you have selected had another method added. The method looks as follows:

/// <summary>
/// Allows this class to be visited by a <see cref="MySpecialVisitor"/>.
/// </summary>
/// <param name="visitor">The visitor.</param>
void SpecialVisit(MySpecialVisitor visitor, int somethingHere)
{
  // before the call
  visitor.VisitMyEntity(this);
  // after the call
}

There you go – the add-in generated all the necessary plumbing for the Visitor pattern. Simple, isn’t it?

Final Thoughts

This article continues the theme of the previous one, i.e., code generation. The CodeBuilder decorator is subclassed by builders such as CSharpBuilder (which were discussed in the previous article), and the whole thing is propagated through model hierarchies via the Visitor pattern. This makes it a very neat and tidy system to use.

The way this add-in was written is similar to the previous one: a C# parser looks at the source code and finds all the classes. Then, we generate the Visitor class from the user’s selections. Adding methods is a bit trickier: since there is no proper API to add code into a class declaration, I use some good old-fashioned hackery to get the job done. I even reformat code after adding it – something that is tricky to do because if you save a file behind the scenes and reformat it in Visual Studio, you get a really weird dialog box asking you whether you prefer the behind-the-scenes file or the one in memory. Luckily, the API is flexible enough to allow us to close open files, write to them, open them again, reformat, and then close the ones that were initially closed. All for the sake of convenience.

That’s all I have to say! If you liked the article, please vote for it! Oh, and as you may have guessed, another pattern-related article is forthcoming. Stay tuned!

License

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

About the Author

Dmitri Nеstеruk
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and F#, though I'm open to suggestions.
 
I'm a Microsoft MVP (Visual C#) since 2009. I run a collective tech blog at DevTalk.net. I use my own editor called TypograFix to typeset articles and blog posts.
 
Like the article and want this implemented in your product? Got a project that can benefit from Microsoft.Net goodness? Then get in touch!
Follow on   Twitter

Comments and Discussions

 
General[Off Topic, or maybe not] hacking Document in VS PinmemberAdrian Pasik16-Apr-09 12:35 
GeneralRe: [Off Topic, or maybe not] hacking Document in VS PinmemberAdrian Pasik17-Apr-09 5:05 

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
Web02 | 2.8.140721.1 | Last Updated 14 Dec 2008
Article Copyright 2008 by Dmitri Nеstеruk
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid