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. ■
Subscribe to Blog updates
Thanks, we've got you!
Eager, Lazy and Explicit Loading with Entity Framework Core
Entity Framework Core (EF Core) supports a number of ways to load related data. There’s eager loading, lazy loading, and explicit loading. Each of these approaches have their own advantages and drawbacks. In this post, let’s have a quick look at each of these ways to load data for navigational prope…
OSS Power-Ups: bUnit – Webinar Recording
The recording of our webinar, OSS Power-Ups: bUnit, with Egil Hansen and Steven Giesel, is available. This was the twelfth episode of our OSS Power-Ups series, where we put a spotlight on open-source .NET projects. Subscribe to our community newsletter to receive notifications about future webi…
Accelerating Your Testing Workflow with Unit Test Creation and Navigation
Unit tests play an important role in our daily development workflow. They help us ensure our codebase's correctness when writing new functionality or performing refactorings to improve readability and maintainability. In the process, we often create new test files that accompany the p…
Introducing Predictive Debugging: A Game-Changing Look into the Future
With the introduction of debugging tools, software developers were empowered to interactively investigate the control flow of software programs to find bugs in live environments. At JetBrains, we've always strived to improve the art of debugging. Besides the more standard things you expect from a de…