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.
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.
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. ■