Diagonactic Enums - Helper methods for C#/VB.Net Enums Truly Constrained to System.Enum (written in C++/CLI and MSIL)

Diagonactic Enums Library
The tl;dr: I wrote an enum library in C++/CLI that actually enforces the generic System.Enum constraint with an eye on raw performance so that working with enums is more readable in code. It's well documented, including help files and Intellisense integration and everything you need to know is in the Apache Licensed library, including (NuGet) install instructions available here.
The Backstory
Every C# developer at one point or another has written an Enum extension method. The type is fundamental enough to be represented by a reserved word (and used far more than many others). It's a very useful feature of the language. And in the process of writing an Enum extension method, most of us have thought, "Geez, I really should do this as a generic implementation! Heck, I could even replace some of those inconvenient statics that make me pass in a typeof(MyEnum) and the extra cast!" So we start...
public static T AddFlags<T>(this T source, T flag) where T: System.Enum
{
    // ...
}
And then the fun begins. Compiler error?! WTF! The ugly truth surfaces. C#, even at version 6.0, does not support this kind of a type constraint.
So I did what you probably did. I settled for an imperfect implementation. My main goal was replacing ToString() which came with a performance penalty that a personal project of mine was really sensitive to. I wrote a few others that rarely were used because the performance penalty of casting wasn't worth the benefit to readability in this same application, but it became a member of my Missing Methods library.
Enum Constraints
The thing is, there's no issue at all with that kind of constraint as far as CLI is concerned. It's completely legal to enforce it in MSIL. And C# can use methods with this constraint. They're valid. In fact, C# genius and prolific blogger Jon Skeet managed to do just that in C# with Unconstrained Melody, using an Interface that's rewritten to the Enum constraint via a post build step with ilasm. It's a really good solution, frankly, and having yet another library for this sort of thing is probably unnecessary. But I'm not one to let unnecessary get in the way of learning.
Enter C++/CLI
The great news is that C++/CLI has absolutely no problem with a System.Enum (or System::Enum in C++/CLI parlance) constraint. I've been re-studying C++ after a long 10 year hiatus and along the way, picked up a couple of books on C++/CLI as well. The problem is that I lacked a project to work on that I would actually want to write that I couldn't write in any other language (ok, well, short of Jon Skeet's awesome hack).
I set out to rewrite my library with proper enum constraints with the goal of being able to use it everywhere without performance penalty and to eliminate the need to ever rewrite a block of code to eliminate performance penalties incurred by the framework's lesser performing methods.
C++/CLI did almost everything except ...
Generics in C++/CLI are a strange beast. So here I have a generic enum. In the normal world, I could apply binary math operations against it directly. In the C++/CLI world, there's no operator that supports those operations, so that's not an option. I have to convert it to the underlying type. No problem? Right? I mean, it's pretty much just a wrapper around the underlying type anyway. Ultimately, it really wasn't that tricky: a reinterpret_cast (and some other acrobatics to ensure GC safety) did the job. Getting it back to its original type was another story. The compiler doesn't know enough about the generic type to let you do the same thing in reverse and stops you at every turn. There's always the Enum.ToObject method, but that's got a cost to it. Small, yes, but the goal here was to eliminate cost -- the point of this library was to serve as a micro-optimized set of operations so that there'd be little trade-off in speed compared against the binary math equivalents.
Enter MSIL
I've played far more with MSIL than I should have being a C# guy. I was the kid who took apart every broken piece of electronic equipment I have ever encountered, so I want to see the guts. Granted, IL isn't assembler, but it's a layer lower than the language itself. The actual code to go to the underlying type is within the method is exactly the same as the code to go backward. The only thing that's different is the method signature. It's a blind cast that says "Hey, Runtime, don't look now but I'm really a TEnum".
I don't like doing this sort of thing. Rewriting parts of a library from its disassembled source is janky, but when there's no other option, well... So the project includes a (poorly written) component called MSIL that's called after the EnumCpp component is built. It's job is to replace the method bodies in MsilConverter.h with the blind cast. If you're not comfortable with that sort of hackery, simply remove the build step. I've written the methods using ToObject calls in C++/CLI and they end up with the same result--just less fast.

Downloading and More
Everything you need to know to use the library is available in its Readme over at GitHub. There's a ton of notes about the code, my thinking and general thoughts about the cost of eggs in the Methodology.md file in the same repo.
Or you can get the package via NuGet over here.
If you run into problems, I'd sure love to know about it -- especially since I've already wired it up in a very important personal project of mine that manages my backups and media at home, so please file an issue.

1 comment :

Tyler Brinkley said...

Great work on this library. I'm the author of the recently released library Enums.NET which has many of the same goals and design considerations you've implemented in your library. If interested I'd appreciate it if you'd check out my library.