ReSharper SDK Adventures Part 6 — Read-only inteface generator

Welcome to another instalment of the ReSharper SDK Adventures! In this part of the series, we are going to talk about code generation and, specifically, about extending the Generate menu with your own actions. As an example, we are going to go through a step-by-step process of creating an action that would generate a read-only interface.

The full source code for the generator action is available here.

Read-only interfaces? In C#?

Indeed! Of course, C# does not have any built-in immutability mechanisms, but that won’t be a problem. Consider a simple Person class:

Let’s say we want to pass this Person into a method that’s not allowed to modify the instance being passed in. How can we do it? Well, one of the ways is to make properties private set;, but what if there are cases when we do want Person to be modifiable? The solution is to define a read-only interface similar to

Notice how the interface properties only have get; and not set;? The Person class can now implement this interface and be passed into methods that aren’t allowed to change it:

So now that we know what we want, let’s see how one would go about adding such an item to ReSharper’s Generate menu.

Generate item provider

We’ll start with the simplest possible construct: a generator item provider. This is a class that is decorated with the [GeneratorProvider] attribute and it implements the IGenerateActionProvider interface. Its purpose is to provide one or more generator workflows through a single method called CreateWorkflow(). In the context of generators, a workflow is a set of steps that involve both showing the generator UI (in our case, we’ll let the user pick which properties make it into the interface) as well as generating the actual code.

Our implementation of the generate item provider is rather simple:

The above code is one of the places where an IDataContext is available, which is a great place to grab hold of the PsiIconManager and get the icon for our generated item. In this case, for obvious reasons we’ll go with the Interface icon. This icon, once acquired, is simply passed into the constructor of our workflow, which we’ll discuss very soon.

Generate action

It is sometimes benefitial to invoke generators as actions. This allows, among other things, to bind a Generate action directly to a keyboard shortcut. To implement this, we need to create an action, i.e. a class decorated with the [ActionHandler] attribute. There’s also a special base class, GenerateActionBase<T>, that is used for providing generator actions. The T variable relates to the provider class we made earlier. So, for our generator, we’ll define an action as follows:

In the above, we specify a textual representation of the action, as well as indicate that a menu should be shown even if our action is the only one in it. The critical piece of information is the argument passed to the ActionHandler: this is the action ID, and it is used in the workflow definition that follows.

Action workflow

The workflow class is a kind of glue that binds things together and allows us to indicate two main things: the action id (as we’ve just defined it) as well as the generator builder where all the generator action actually happens. Please note that the generator builder is referred to by name or, more precisely, by kind. Here is the definition of our workflow:

Just to reiterate, in the above,

  • The "ReadOnlyInterface" string refers to the generator builder (we’ll meet it in a moment).

  • The "Generate.ReadOnlyInterface" is the action ID.

Other entries are used to provide textual information as well as specify the action group where our action belongs – in this case, a group related to CLR languages.

Generator builder

The generator builder is the part that makes explicit which elements and options appear in the generator dialog, and what code actually gets generated. Our implementation of a read-only interface consists of these steps:

  • Find all suitable class properties and present a dialog box for user to pick them

  • Create the new interface and insert it into the same file

  • Ensure our original class implements the interface

The generator builder is just a class that is decorated with the [GeneratorBuilder] attribute that indicates the generator kind and also the language the builder corresponds to, e.g.:

This class is expected to override only one method called Process() where generation takes place after the user presses the Ok button on the Generate dialog. I’ll use CPS (continutation passing style) here to better demonstrate the sequence of operations as we build the interface. Let’s start with the outline:

In the above, ReadOnlyInterfaceBuilderWorkflow is a static class that uses the CPS paradigm. Its Start() method performs a rudimentary check and then sends us off to create the new interface:

We create an empty interface to begin with…

… and before adding its members we add it to the containing namespace:

Now that the interface has been added to the file we’re in, we can take each of the properties the user selected and add the appropriate member. I’ve cleverly omitted the process of gathering those properties to begin with because that will appear in the subsequent section – for now, we just assume that the CSharpGeneratorContext that we’ve been taking through all our method calls contains an InputElements property that has all the items the user selected.

Thus, we add properties to the type as follows:

And, finally, the easy part — making sure that the original class implements the interface:

And that’s it! As you can see, we’ve used context.ClassDeclaration to get at the original class, which sure makes manipulating things easier! This is almost it for the generator, but where did those properties in context.InputElements come from?

Generator element providers

The items that appear as check boxes in the Generate UI and subsequently appear in context.InputElements are supplied by generator element providers. Such a provider is a class decorated with the [GeneratorElementProvider] attribute and inheriting from GeneratorProviderBase<TGeneratorContext> where TGeneratorContext depends on the language. In our case, the provider looks as follows:

This class has only one important method called Populate(). This method gets a CSharpGeneratorContext as a parameter, and can use that context both to get at the object of our code generation efforts (i.e., the class we’re working with) as well as exposing the ProvidedElements collection that we can populate with, in our case, all suitable properties.

The type of each element is IGeneratorElement. This interface has a number of concrete implementors (e.g., for XAML events). The type we’ll use is a simple GeneratorDeclaredElement<T> where T is the type of declared element that we’ll be providing. Since generators typically provide a wide variety of members, and not just properties, we may as well use something very wide-ranging, such as e.g. ITypeOwner. All that matters is that we can correctly supply the declared element of the member.

So here’s what the implementation of Populate() looks like. First, we do a few rudimentary checks:

And subsequently we go through each of the available property declarations, filter out unsuitable ones, wrap the rest in GeneratorDeclaredElement entities and add them to the ProvidedElements collection:

Now, how does the generator builder know to use this item provider? It’s all in that kind string that we decorated both the builder and element provider classes with. That’s what made the magic happen.

Conclusion

Making a generator item is not that difficult, though it is perhaps a bit more convoluted than a simple context action. The possibilities for code generation are truly limitless, so you can simply take the source code, tweak it and you’ll be making your own generators in no time at all. Good luck!

This entry was posted in ReSharper Tips&Tricks and tagged , . Bookmark the permalink.

8 Responses to ReSharper SDK Adventures Part 6 — Read-only inteface generator

  1. Richard says:

    Of course, your example doesn’t actually prevent the method from modifying the underlying data:

    public void Foo(IReadOnlyPerson person)
    {
    ((Person)person).Age = 42; // OK
    }

    It’s useful as an indication of intent; it’s not so good at preventing buggy or hostile code from making changes.

  2. Dmitri Nesteruk says:

    @Richard: you are right, this is only a guidance mechanism – it won’t prevent people from modifying things if they intend to.

  3. L says:

    Hi,
    Can I set for Generate UI to show provided elements as exclusive so show radio button instead of check boxes?
    And can I create a GeneratorBuilder without Generate UI window (GeneratorElementProvider)?
    Thanks!

  4. Dmitri Nesteruk says:

    No, I’m afraid you cannot substitute UI elements in the Generate window. As for a UI-less generator, this is certainly possible – check out this listing for a no-UI generator workflow base class.

  5. L says:

    Thanks! I have an another issue: how can I create and add a new project folder?

  6. Dmitri Nesteruk says:

    @L I would recommend EnvDTE’s own ProjectItems.AddFolder call

  7. Sydney says:

    I copied this source into a project and WorkfowResult is not found – is there a particular reference I need for that?

    • Dmitri Nesteruk says:

      Hi, I suspect the API might have changed since this blog post was written. Which version of ReSharper are you using?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">