Generic constraints for enums and delegates

As most readers probably know, C# prohibits generic type constraints from referring to System.Object, System.Enum, System.Array, System.Delegate and System.ValueType. In other words, this method declaration is illegal:

public static T[] GetValues<T>() where T : struct, System.Enum
{
    return (T[]) Enum.GetValues(typeof(T));
}

This is a pity, as such a method could be useful. (In fact there are better things we can do… such as returning a read-only collection. That way we don’t have to create a new array each time the method is called.) As far as I can tell, there is no reason why this should be prohibited. Eric Lippert has stated that he believes the CLR doesn’t support this – but I think he’s wrong. I can’t remember the last time I had cause to believe Eric to be wrong about something, and I’m somewhat nervous of even mentioning it, but section 10.1.7 of the CLI spec (ECMA-335) partition II (p40) specifically gives examples of type parameter constraints involving System.Delegate and System.Enum. It introduces the table with "The following table shows the valid combinations of type and special constraints for a representative set of types." It was only due to reading this table that I realized that the value type constraint on the above is required (or a constructor constraint would do equally well) – otherwise System.Enum itself satisfies the constraint, which would be a Bad Thing.

It’s possible (but unlikely) that the CLI doesn’t fully implement this part of the CLR spec. I’m hoping that Eric’s just wrong on this occasion, and that actually there’s nothing to stop the C# language from allowing such constraints in the future. (It would be nice to get keyword support, such that a constraint of "T : enum" would be equivalent to the above, but hey…)

The good news is that ilasm/ildasm have no problem with this. The better news is that if you add a reference to a library which uses those constraints, the C# compiler applies them sensibly, as far as I can tell…

Introducing UnconstrainedMelody

(Okay, the name will almost surely have to change. But I like the idea of it removing the constraints of C# around which constraints are valid… and yet still being in the key of C#. Better suggestions welcome.)

I have a plan – I want to write a utility library which does useful things for enums and delegates (and arrays if I can think of anything sensible to do with them). It will be written in C#, with methods like this:

public static T[] GetValues<T>() where T : struct, IEnumConstraint
{
    return (T[]) Enum.GetValues(typeof(T));
}

(IEnumConstraint has to be an interface of course, as otherwise the constraint would be invalid.)

As a post-build step, I will:

  • Run ildasm on the resulting binary
  • Replace every constraint using EnumConstraint with System.Enum
  • Run ilasm to build the binary again

If anyone has a simple binary rewriter (I’ve looked at PostSharp and CCI; both look way more complicated than the above) which would do this, that would be great. Otherwise ildasm/ilasm will be fine. It’s not like consumers will need to perform this step.

As soon as the name is finalized I’ll add a project on Google Code. Once the infrastructure is in place, adding utility methods should be very straightforward. Suggestions for utility methods would be useful, or just join the project when it’s up and running.

Am I being silly? Have I overlooked something?

A couple of hours later…

Okay, I decided not to wait for a better name. The first cut – which does basically nothing but validate the idea, and the fact that I can still unit test it – is in. The UnconstrainedMelody Google Code project is live!

51 thoughts on “Generic constraints for enums and delegates

  1. This would be great. But I have a question. What exactly will you put up in Google Code? An infrastructure to make that magic automatically (the ildasm/replace/ilasm part) on client code, a utility library with that as a post-build step, or both?

  2. Indeed, the CLR seems to support this, as the following F# code works (hoping it won’t be mangled by HTML escaping):

    let GetValues< 'a when 'a :> System.Enum and ‘a : struct>() = System.Enum.GetValues(typeof< 'a>) :?> ‘a[]

  3. Great news!!! I look forward to this since I’ve been trying to write some Enum helpers (generic of course) and I’ve resorted to reflection to check whether the generic/type parameter is really an enum or just another value type.

  4. I agree that this is a silly constraint in C#, but I have two problems with your proposal:
    1. It adds a post-build step that is required to use your library… I can’t say I like it. Even if someone is already using PostSharp he’d still need to run yet another post-build step.
    2. Perhaps there is a problem with this constraint you haven’t thought of – I’d suggest discussing this with Eric Lippert since he seems to know more about the problem than we do.

  5. @configurator: No, you don’t need a post-build step to use my library. You need a post-build step to *build* the library. Go ahead, download the binary now – all you need is the assembly, which doesn’t have any extra dependencies.

    As for problems with the constraint – I’ll certainly welcome Eric’s input, which I’m sure will be forthcoming soon enough. It’s very odd that C# prohibits constraints which are explicitly demonstrated in the CLI spec. I wonder whether the designers for the CLI and language got out of step with each other at some point, and one bit of failed communication has left us with this language oddity.

  6. I stand corrected… That’s what happens when I don’t think things through. If the post-build step is only to compile the library, it wouldn’t be a problem at all.

  7. git svn’ing the code as we speak. By the way, I really like the name as it stands. +1 for Unconstrained Melody!

  8. @Martinho: Which version are you using? Build 4.5.1231.7 seems to work okay with it in VS2008. I haven’t tried VS2010 yet. What symptoms are you seeing?

    (I had a tweet from a ReSharper developer who complained I was adding more work for them. I’m not sure whether he was serious – I hope not!)

  9. Very interesting; I’ve heard of non-C# implementations before, but very cool. Re the omissions; in particular, it would have been nice if things like `Expression` had been able to use delegate constraints (pity). I guess you’ve already covered most of the obvious methods (for enums; check/set bit flags, list values, etc). The only problem I can see is that even as a library it might make for problems with generics – i.e. if I can’t write my own `SomeType` that takes an enum-T, then I presumably can’t prove to the compiler that I can call `YourUtility.SomeMethod`.

  10. A few points:

    what do consumers of the library experience in the c#,f#,C++/CLI compilers if they violate this constraint?

    If you’re doing a library doing it in/with C++/CLI and then putting in the code to rapidly convert an enum to an int/long/byte/uint/ulong etc without boxing would be excellent.
    We found the only way to do this was with things like:

    static int GetAsInt(T t)
    {
        return *((Int32*) (void*) (&t)); 
    }				
    

    This then lets you write a fast EnumEqualityComparer with fast access to make ahashcodes and perform equality testing then have this accessible from a nice static property on some generic class like so:

    public class EnumUtils
    {
        public static IEqualityComparer Default
        {
            get { /* return the right int/long/ect comparer*/ }
        }
    }
    
  11. @Shuggy: Consumers will get a compile-time failure if they violate the constraint. At least, that’s what happens in C#…

    I’ll think about the conversion side of things. What do you want it to do if the base type isn’t the type you’re converting to – perform a conversion for you, or throw? I’m not quite clear on what’s going on here… (In C# I’d usually just cast to the underlying type.)

  12. “(In C# I’d usually just cast to the underlying type.)”

    With constraints on enums you can’t cast to the underlying type cleanly.
    I would say that, the cleanest thing to do is to put in the (messy) code that switches on the underlying type, pull that value out safely and cleanly. then have the various conversions handled to the requested type, throwing for any which would involve data loss.

    An explicit CastAsInt32(), CastAsInt64() etc can do the alternate which is to just do whatever

    T x = (T)myEnumValue;
    U y = (Y)x;

    would do if T and U were compile time known integral values…

    The idea is to be safe, to be as efficient as possible while still providing behaviour equivalent to non generic code attempting to do the same thing (so safe conversions are allowed).

  13. @Shuggy: I don’t know C++/CLI, and learning it would take longer than building this bit of infrastructure 🙂

  14. Would you object to it being moved to C++/CLI?

    Obviously the functionality I want to add would put it into the realms of unsafe code so fair enough, how about a separate C++ project within the solution for those sorts of things?

  15. @Shuggy: I’d rather keep what I can in C#, but you’re welcome to create an UnconstrainedMelody.Unsafe assembly if you’d like.

    I’m hoping to do some evil things with dynamic methods do allow stuff like ToInt32 etc without boxing…

  16. @skeet: I am using 4.5.1231.7 as well. It keeps complaining that Action does not satisfy IDelegateConstraint. It builds ok, it’s just ReSharper that gets annoying.

  17. @Martinho: Just to be clear, is this a problem while you’ve got the UnconstrainedMelody solution itself open, or another solution referencing the rewritten assembly? I’m not too worried about the former version, as I don’t expect many people to build it themselves.

  18. I’ve just discovered that ReSharper works better if you build in release mode. My guess is that otherwise some details about where the code comes from is left in the assembly, and ReSharper then uses the source code for the constraints instead of the binary…

  19. I saw that the GetDescription method wasn’t implemented yet in 0.0.0.2 so I implemented it to test out the library. I’m a fan already. This will be very useful. Do you plan to use the System.ComponentModel.DescriptionAttribute in the library?

    using System.ComponentModel;

    public static string GetDescription(this T item) where T : struct, IEnumConstraint
    {
    FieldInfo fieldInfo = item.GetType().GetField(item.ToString());
    DescriptionAttribute[] attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
    return (attributes != null && attributes.Length > 0) ? attributes[0].Description : null;
    }

  20. @Slaks: Very neat… although unfortunately the same trick can’t be used for delegates as far as I can tell. (For other readers: it only works for enums because an enum is a *value type* which is implicitly convertible to Enum, but Enum itself isn’t a value type.)

    I’ll definitely stick with my current approach – extension methods are important here – but kudos for the evil trick 🙂

    @Taylor: Yup, that’s the attribute in question. I’m going to preload the results though in EnumInternals. Should be able to implement it tonight…

  21. Similarly to GetDescription in 0.0.0.2, it would be nice to see the reverse (description => enum) like below:

    public static T? GetEnumFromDescription(string description) where T : struct, IEnumConstraint
    {
    foreach (FieldInfo field in typeof(T).GetFields())
    {
    DescriptionAttribute[] attributes = field.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
    if (attributes != null && attributes.Length > 0 && attributes[0].Description == description)
    {
    return ParseName
    (field.Name);
    }
    }
    return null;
    }

  22. @Skeet:

    It actually would work for delegates & arrays, except that there’s no way to exclude System.Delegate and System.Array at compile time. (The point of the struct constraint is just to exlcude System.Enum itself)

  23. @SLaks: Yes, that’s what I meant by “the same trick” – the value type constraint used to exclude System.Enum.

  24. I used to write most of my .Net in a language called Oxygene, which allowed these contraints (Delegate & Enum) just fine.
    There’s nothing wrong with it from the CLR POV.
    Its just that C# is far stricter than it has to. AFAIR, the argument was “that you couldn’t do anything useful with those types in generic code”.
    Well, I could move runtime parameter checking to compile time checking and provide a better API.
    Hopefully they see the light some day…

  25. Hello,

    this seems nice if it worked.

    But when i try to use it, it complains about the following (this is when trying to run the tests included):

    The type ‘UnconstrainedMelody.Test.BitFlags’ cannot be used as type parameter ‘T’ in the generic type or method ‘UnconstrainedMelody.Flags.IsFlags()’. There is no boxing conversion from ‘UnconstrainedMelody.Test.BitFlags’ to ‘UnconstrainedMelody.IEnumConstraint’. D:\Projects\Unconstrained Melody\UnconstrainedMelody.Test\FlagsTest.cs 11 27 UnconstrainedMelody.Test

    It complains about this everywhere a enum is used as the generic type.

  26. @Martin: Do you run ReSharper? You may well find that ReSharper complains even though the real compiler doesn’t – that’s my experience, anyway.

    Try just using it from a completely separate project, and you should find it works fine.

  27. (If that’s not the problem, check whether it really managed to do the appropriate post-build step – it may have complained if your ilasm/ildasm is in a different place to mine.)

  28. It seems like the dll from 0.0.0.2 on google code is not “rewritten” or not up-to-date. (it was the one i tried to use with the tests because i cant compile the source code)

  29. @Martin: Ah, okay – I didn’t realise you were using the binary distribution. Hmm. Will download it myself from scratch and check. Thanks for the heads-up.

  30. Skeet:
    I am not running ReShaper.
    I found out about this problem because i was using the dll on google code in my project but I couldnt compile (then tried messing around with the source code).

    I just got it to build. (It couldnt find the right path for the SDK – i am running Vista 64 bit version – dont know what folder the Enum Environment.SpecialFolder.ProgramFiles is reffering to – hardcoding the path worked).

    The tests works fine with dll which was build with the project source code (not with the one found on google code – missing some methods it seems)

    Of some reason i am having troubles making it work when including only the dll in my project.
    I must be doing something wrong.

  31. @Martin: Which version of Visual Studio and .NET are you using? I’ve just tried this tiny sample program against the distribution DLL, and it works fine – could you try it?

    using System;
    using UnconstrainedMelody;

    enum Foo
    {
    Bar
    }

    class Test
    {
    static void Main()
    {
    foreach (Foo value in Enums.GetValues())
    {
    Console.WriteLine(value);
    }
    }
    }

    (I suspect that will get mangled in terms of formatting, but I’m sure you can disentangle it.) Note that I’d expect the tests not to build against the distributed DLL as the tests are newer than the DLL. But you should be able to build against the distributed DLL with no problems…

    I suspect that comments on this blog aren’t the best way to get to the bottom of this. Could you mail me directly (skeet@pobox.com) and we can sort it out that way?

    Jon

  32. I wrote a C++ library to use the enum constraint to do the various common utility methods discussed above. If I can get my company’s blessing, I can make that piece of code public and add it to your google code project.

    I didn’t read all the comments, but in case no one else has mentioned it, when consuming the C++ library from C#, intellisense doesn’t understand the Enum constraint and only shows the constraint as T where T : struct. It lets you put any old struct in as a parameter (or the receiver object of an extension method), but it does give a compiler error once you try to build the solution. It’s just a pity that any enum extension methods you might make show up on all structs in the intellisense.

    I don’t believe they intend on fixing this for VS2010, so perhaps an addin or other extension could modify the intellisense to work correctly with the constraints.

    Tim

  33. I know I said I wrote a C++ library, but I like your solution better since it allows the code to be written in C#. Very creative! I had to make a few tweaks to get your re-writer to work: the arguments we not quoted properly, and also I manually created the Rewritten folder. I also changed the changer to take in the input and output filenames as arguments in the post build step.

    Now the only thing stopping me from being able to use this is that I need to do the recompilation using a keyfile (easy change) and I need to to generate XML documentation (should be another easy change).

    If your interested, I can contribute these changes if and when I make them. I also have some of my own utility methods that might be worth contributing as well.

  34. I’d be very happy to receive those patches, yes. I thought I was already generating XML documentation, but maybe not…

    The bit about the rewriter not working out of the box doesn’t entirely surprise me – I wrote that bit in a fairly quick and dirty way so that I could get going on the interesting stuff 🙂

  35. @Frank: Yes, that’s the whole point. I referred to that in the very first sentence of the post. The idea is to work around that limitation, which only exists for type constraints *declared* in C#.

Comments are closed.