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!

Published Thu, Sep 10 2009 21:09 by skeet

Comments

# re: Generic constraints for enums and delegates

With Postsharp should be prety easy. I guess. :-)

I'll try to create a working sample tomorrow.

Thursday, September 10, 2009 3:32 PM by Miha Markic

# re: Generic constraints for enums and delegates

If this works... it will be awesome.  I have had many IM conversations where we curse the fact that System.Enum cannot be a constraint.

Thursday, September 10, 2009 3:46 PM by Charles Feduke

# re: Generic constraints for enums and delegates

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?

Thursday, September 10, 2009 4:20 PM by Martinho Fernandes

# re: Generic constraints for enums and delegates

@Martinho: Both. And I'm nearly ready to do it...

Thursday, September 10, 2009 4:24 PM by skeet

# re: Generic constraints for enums and delegates

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[]

Thursday, September 10, 2009 4:25 PM by Keith

# re: Generic constraints for enums and delegates

Mono.Cecil can do the binary rewrite and is very small and neat (just like a finger of fudge).

Thursday, September 10, 2009 5:18 PM by Leaf Garland

# re: Generic constraints for enums and delegates

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.

Thursday, September 10, 2009 5:36 PM by Andrei Rinea

# re: Generic constraints for enums and delegates

@Leaf: Will give Mono.Cecil a try tomorrow.

Thursday, September 10, 2009 5:51 PM by skeet

# re: Generic constraints for enums and delegates

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.

Thursday, September 10, 2009 5:55 PM by configurator

# re: Generic constraints for enums and delegates

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

Thursday, September 10, 2009 6:05 PM by skeet

# re: Generic constraints for enums and delegates

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.

Thursday, September 10, 2009 6:09 PM by configurator

# re: Generic constraints for enums and delegates

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

Thursday, September 10, 2009 6:39 PM by Martinho Fernandes

# re: Generic constraints for enums and delegates

Very nice, except that ReSharper totally freaks out with this :(

Thursday, September 10, 2009 7:19 PM by Martinho Fernandes

# re: Generic constraints for enums and delegates

@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!)

Friday, September 11, 2009 1:38 AM by skeet

# re: Generic constraints for enums and delegates

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<T>` 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<T>` that takes an enum-T, then I presumably can't prove to the compiler that I can call `YourUtility.SomeMethod<T>`.

Friday, September 11, 2009 2:33 AM by Marc Gravell

# re: Generic constraints for enums and delegates

Interesting post.

I have written an implementation of this feature using PostSharp:

www.postsharp.org/.../generic-constraints-for-enums-and-delegates

It was not that hard once one knows how to do it (30 minutes).

Friday, September 11, 2009 2:43 AM by Gael Fraiteur

# re: Generic constraints for enums and delegates

Cool idea Jon ! It's really infuriating that we can do this in C++/CLI or MSIL, but not in C#...

Friday, September 11, 2009 3:47 AM by Thomas Levesque

# re: Generic constraints for enums and delegates

Nice idea!

You should also answer my question on SO on this:

stackoverflow.com/.../7244

That also includes the main extension that I'd want - an easier to read way to check flags enums.

Keith

Friday, September 11, 2009 4:00 AM by Keith Henry

# re: Generic constraints for enums and delegates

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:

<pre>

static int GetAsInt(T t)

{

   return *((Int32*) (void*) (&t));

}

</pre>

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:

<pre>

public class EnumUtils<T>

{

   public static IEqualityComparer<T> Default

   {

       get { /* return the right int/long/ect comparer*/ }

   }

}

</pre>

Friday, September 11, 2009 5:17 AM by ShuggyCoUk

# re: Generic constraints for enums and delegates

@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.)

Friday, September 11, 2009 5:25 AM by skeet

# re: Generic constraints for enums and delegates

"(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).

Friday, September 11, 2009 6:10 AM by ShuggyCoUk

# re: Generic constraints for enums and delegates

Finished writting PostSharp plugin as well...only to see that Gael did it already. Oh well.

Friday, September 11, 2009 6:22 AM by Miha Markic

# re: Generic constraints for enums and delegates

incidentally Jon, since you linked to weblogs.asp.net/.../The-Case-of-the-Missing-Generic-_2800_Parse-Method_2900_.aspx any reason you don't just implement the library in c++/CLI to avoid the postsharp/iddasm post build step?

Friday, September 11, 2009 7:35 AM by ShuggyCoUk

# Generic constraints for enums and delegates - Jon Skeet

Thank you for submitting this cool story - Trackback from DotNetShoutout

Friday, September 11, 2009 7:40 AM by DotNetShoutout

# re: Generic constraints for enums and delegates

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

Friday, September 11, 2009 8:44 AM by skeet

# re: Generic constraints for enums and delegates

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?

Friday, September 11, 2009 10:33 AM by ShuggyCoUk

# re: Generic constraints for enums and delegates

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

Friday, September 11, 2009 11:11 AM by skeet

# re: Generic constraints for enums and delegates

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

Friday, September 11, 2009 1:32 PM by Martinho Fernandes

# re: Generic constraints for enums and delegates

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

Friday, September 11, 2009 1:44 PM by skeet

# re: Generic constraints for enums and delegates

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

Friday, September 11, 2009 3:26 PM by skeet

# Generic constraints for enums and delegates

DotNetBurner - burning hot .net content

Saturday, September 12, 2009 4:57 PM by DotNetBurner - C#

# re: Generic constraints for enums and delegates

This actually is possible in raw C# 2.0, except for extension methods.  See my answer to the referenced question.  (stackoverflow.com/.../1416660)

Saturday, September 12, 2009 9:21 PM by SLaks

# re: Generic constraints for enums and delegates

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<T>(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;

}

Sunday, September 13, 2009 1:11 AM by Taylor Leese

# re: Generic constraints for enums and delegates

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

Sunday, September 13, 2009 1:42 AM by skeet

# re: Generic constraints for enums and delegates

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

public static T? GetEnumFromDescription<T>(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<T>(field.Name);

        }

    }

    return null;

}

Sunday, September 13, 2009 2:10 AM by Taylor Leese

# re: Generic constraints for enums and delegates

@Taylor: Yup, I will have a reverse mapping too.

Sunday, September 13, 2009 2:31 AM by skeet

# re: Generic constraints for enums and delegates

@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)

Sunday, September 13, 2009 5:04 PM by SLaks

# re: Generic constraints for enums and delegates

@SLaks: Yes, that's what I meant by "the same trick" - the value type constraint used to exclude System.Enum.

Monday, September 14, 2009 12:23 AM by skeet

# re: Generic constraints for enums and delegates

Will you be using String.Split(',') and the ilk for flags support?

Tuesday, September 15, 2009 4:43 PM by Marc Brooks

# re: Generic constraints for enums and delegates

@Marc: I don't have anything for parsing combinations of flags yet. I need to think on it for a while.

Tuesday, September 15, 2009 4:53 PM by skeet

# Generically Constraining F# – Part I

Generic constraints inside .NET has always been a fun enterprise, especially given how C# handles them 

Tuesday, September 15, 2009 11:14 PM by Matthew Podwysocki

# Generically Constraining F# – Part I

Generic constraints inside .NET has always been a fun enterprise, especially given how C# handles them

Tuesday, September 15, 2009 11:39 PM by Matthew Podwysocki's Blog

# F# Discoveries This Week 10/04/2009

I’m back from my three week vacation and am just about buried in fascinating functional programming links. 

Sunday, October 04, 2009 8:59 PM by Rick Minerich's Development Wonderland

# re: Generic constraints for enums and delegates

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

Wednesday, October 07, 2009 2:15 PM by Robert Giesecke

# re: Generic constraints for enums and delegates

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<T>()'. 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.

Monday, October 12, 2009 12:24 PM by Martin F

# re: Generic constraints for enums and delegates

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

Monday, October 12, 2009 1:03 PM by skeet

# re: Generic constraints for enums and delegates

(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.)

Monday, October 12, 2009 1:04 PM by skeet

# re: Generic constraints for enums and delegates

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)

Monday, October 12, 2009 1:15 PM by Martin F

# re: Generic constraints for enums and delegates

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

Monday, October 12, 2009 1:34 PM by skeet

# re: Generic constraints for enums and delegates

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.

Monday, October 12, 2009 3:15 PM by Martin F

# re: Generic constraints for enums and delegates

@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<Foo>())

       {

           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

Monday, October 12, 2009 3:30 PM by skeet

# re: Generic constraints for enums and delegates

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

Sunday, October 25, 2009 6:57 PM by Timothy Bussmann

# re: Generic constraints for enums and delegates

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.

Sunday, October 25, 2009 7:51 PM by Timothy Bussmann

# re: Generic constraints for enums and delegates

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 :)

Sunday, October 25, 2009 9:52 PM by skeet

# re: Generic constraints for enums and delegates

Beware, C# 3.0 does not allow this:

[C# 3.0 Specification, S.269]

download.microsoft.com/.../CSharp%20Language%20Specification.doc

"The type must not be one of the following types: System.Array, System.Delegate, System.Enum, or System.ValueType."

ciao Frank [MVP C#]

Wednesday, November 25, 2009 1:34 PM by Frank Dzaebel

# re: Generic constraints for enums and delegates

@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#.

Wednesday, November 25, 2009 1:46 PM by skeet