ReSharper SDK Adventures Part 5 — D-style Mixins in C#

This is Part 5 of the ReSharper SDK Adventures — a series dedicated to in-depth coverage of ReSharper plugin development. This series was previously hosted on DevTalk, but will now be appearing on the JetBrains .NET Tools blog.

In case you missed the first four parts of the series, here’s a brief recap:

  • Part 1 showed how to create an element problem analyzer, highlightings and a quick-fix to turn Math.Pow() calls with integral powers into inlined multiplications.

  • Part 2 demonstrated improvements to the example in Part 1, including user-configurable settings as well as a code cleanup module.

  • Part 3 has shown how to create a simple action to paste numeric data in CSV format into C# as an array.

  • Part 4 gave an overview of the Agent Mulder plugin and an explanation of how it uses SSR (Structural Search & Replace) to programmatically search code for specific patterns. It also showed how to use gutter marks and suppress unwanted inspections.

In this part of the series, I’d like to show how you can use ReSharper to effectively get D-style mixins in ordinary C# code.

Mixins!

The notion of a mixin in the D programming language implies, simply, some code that is inserted as text by the compiler. While the C# compiler doesn’t provide functionality to simply inject elements at compile-time, it’s entirely possible to get the same effect at ‘design time’, i.e. right in code.

We’re going to implement the simplest possible mixin construct — a context action that looks for a MixinAttribute declaration on a class, executes the code in the mixin parameter, takes the results of the execution and splices them right into the class. For example:

would, after execution, result in the following

The above assumes the presence of a MixinAttribute which is only used as a convenient means of storing the program to execute. Note the use of two double quotes. The attribute can be defined simply as follows:

Now that we know what we want, let’s discuss how we’re going to implement this.

Attribute Detection and Analysis

We’re building a very simple context action, so the first order of the day is to determine where it’s applicable. And, strictly speaking, it’s applicable wherever there’s a Mixin attribute – we don’t care about the namespace it lives in.

Attempting to identify the attribute’s contents (together with a layer of null checks) is far more tedious, which is why I typically employ the Maybe monad to come up with the following search for the attribute argument:

This notation may seem weird, but it cuts down on excessive nesting and makes the null checks implicit. Now that we are here, we can use a simple StringBuilder to build the actual program we’re going to execute. All we need to do here is put together some using statements, a wrapper class and the contents of that argument the user entered:

Now that this is in place, we perform in-memory compilation of the program. I won’t show the in-process compiler because it’s boilerplate code – you can find it here.

The above compiles the code, executes it, concatenates the result, surrounds it with line breaks and inserts into the current document just before the class’ closing curly brace.

And that pretty much concludes the implementation of our mixin action. You can find the full implementation here.

Use Cases?

So what’s the use case for something like this? Well, how about creating a storage struct for an arbitrary N-by-M matrix? All we have to do is define the structure as follows:

… and after applying the mixin, you’ll get 9 generated fields, all correctly named:

Of course, this kind of ’metaprogramming’ behavior is not commonly required, but when you do need to generate structures in this way, the action presents a more transparent and convenient alternative than using T4 or AOP-style postprocessing. ■

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

6 Responses to ReSharper SDK Adventures Part 5 — D-style Mixins in C#

  1. Arseny says:

    Isn’t it better to use another language (eg. Nemerle) for such a task? Macro support in Nemerle would do same task much easier, I think.

  2. Dmitri Nesteruk says:

    @Arseny: if you can use Nemerle, Boo or some other language with metaprogramming capabilities, that is of course a better option, especially seeing how you’d get the API not to just inject text but to traverse the AST and make changes, too. If you are stuck with C#, however, the range of options is severely limited.

  3. Ivan says:

    This is neat, but I wasn’t sure when the mixin replacement happened. Is it a one-off insertion when the user applies the attribute? Is it re-run every time the code is compiled? Does the mixed-in code get substituted into the .cs file or does that happen only in a ‘temporary’ .cs file which Resharper passes to the C# compiler instead of the real one? Is Intellisense aware of the mixed-in code? Keen to learn more!

  4. andrew says:

    Isnt builder or factory pattern does exactly the same?
    In case you want to do it dynamicly, why not to use dynamic/reflection or tools – unity/entlib/etc?
    In case you want to be all type safe and achive simmilar effect by treating code as data composable why not to use expression trees, linq?

  5. Guido says:

    I’ve really enjoyed this series, I hope you can do a post on JavaScript plugin development.

  6. nesteruk says:

    @Ivan in the presented implementation, the replacement only happens when the action is triggered. It gets inserted in the original file, inside the containing class. As it stands, the current implementation does not handle collisions, so if you inject the code twice in a row you’ll get duplicates.

    @andrew the main rationale for doing it dynamically is the same as T4 – that certain usage patterns are best implemented with code generation. For example, if you need to have a class with properties named AA to EE (including e.g., CD), this is the kind of task that this implementation can help you automate.

    @Guido thanks, more coming soon!

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="">