I've been creating a fluent argument validation library; see parts one, two and three if you need to catch up.
Generics are brilliant. Wonderful things. But there are a couple of things about them that annoy me. For example, why can't I do the following?
public class SomeClass
{
public void SomeMethod<T>(T value) where T : class...
public void SomeMethod<T>(T value) where T : struct...
}
I.e. use a different method overload depending on whether my type is a reference or a value type? A type cannot be both a reference and value type at the same time so surely we have two different method signatures? Yes, I know there are good reasons why not. But still... I want to be able to do this!
Another one that annoys me (and is relevant to this post!) is not being able to to specify my type must be an enum, i.e. something like:
public void Argument<T>(T value) where T : enum
I want to be able to create a validation method that checks if an enum value is defined. But that the same time I don't want to have people pass any value type they like in. Whilst I can quite happily check at runtime that a type is an enum it just feels like something I should be doing at compile time...
Of course what makes this really annoying is that you can specify an enum as a type constraint in IL! Whilst I'm quite happy for C# to restrict me in some ways (e.g. in IL you can throw an object of any type whereas C# restricts it to Exception and sub-classes) this one I'm not happy about. Mainly because it affects me personally, I'll admit. But is that not a good enough reason?
Guess I could rewrite the thing in a language that does allow it. Nah. I like C#. I could write my own language that is an extension of C#. Whilst I'd like to do that one day I think it might be overkill for this... What if I decompiled the assembly the C# compiler generated and changed the offending line? That might work... C# would have to respect the type constraint even if it cannot be expressed in C#. Let's try that...
Turns out it works too. Almost. Turns out I bump into my first generic annoyance, that of methods with the same signature but different type constraints. So I cannot do the following pseudo-C#:
public class Validate
{
public void Argument<T>(T argument, string parameterName) where T : class...
public void Argument<T>(T argument, string parameterName) where T : enum...
}
Guess I'll just have to live with that one and create an EnumArgument method on my Validate class. So what IL do I need to rewrite? Well firstly I'll have to rewrite the initial generic method on my Validate class. Luckily IL method signatures are fairly readable. The C# method signature:
public static IEnum<T> EnumArgument<T>(T argument, string parameterName) where T : struct
Becomes:
.method public hidebysig static class KWatkins.Validation.Argument.IEnum`1<!!T>
EnumArgument<valuetype .ctor ([mscorlib]System.ValueType) T>(!!T argument, string parameterName) cil managed
The type constraints are quite complex compared to C#. The type has to be a value type, has to have a parameterless constructor and has to inherit from System.ValueType. C# hides the second two things from us as in C# we can infer that if something is a value type then it must have a parameterless constructor and inherit from System.ValueType. Anyways its simple enough to change this to have an enum type constraint:
.method public hidebysig static class KWatkins.Validation.Argument.IEnum`1<!!T>
EnumArgument<valuetype .ctor ([mscorlib]System.Enum) T>(!!T argument, string parameterName) cil managed
Done. Change ValueType to Enum and walk away. Nice and simple. I also have to rewrite the IL on the class that actually does the validation but that's basically the same replacement as above.
How do I rewrite the IL though? Luckily .NET comes with a tool called ildasm that decompiles any given assembly into IL. And then Visual Studio comes with a tool called ilasm that compiles IL into an assembly. Therefore I can create a command line application that can run as a post build command for my validation assembly. It can call ildasm to disassembly the assembly, do some crude string replacement on the IL and then pass it back into ilasm to turn the thing back into an assembly.
And that's that. I now have a Validate.Argument<T> method that will only accept an enum for it's type parameter. And that's pretty much it for the vaguely interesting stuff in my argument validation library too. So I'll wrap things up for version one of the library in the next post.