Death by a thousand cuts


As you might have noticed from my posts in the past I really dislike it when there is a part of the C# language that seems to be unnecessarily lacking.  For example, not being able to take a delegate to a property.  It’s a perfectly reasonable thing to do.  You run into it in practice.  And, it’s something that is completely possible to do without any difficulty in the language.  Having methods be able to do this but not have properties (which are just glorified methods) have the same capability just annoys me.  Similaryly, not having something as nice as covariant return types is also quite aggravating.  It could be added without breaking any code, and it ends up making life much nicer for both API developers and consumers.

There are an enormous number of these “nuisances”, and (unsurprisingly) we’ve found one more.  It’s often the case that you want to have an operation that works specifically on an enum.  For example you might want to take the unsafe Enum.Parse method and replace it with a typesafe one.  Currently Enum.Parse looks like.

class Enum

{

public static Enum Parse(Type enumType, string enumMemberName) { … }

}

 

To use it you have to write code like:

Color c = (Color)Color.Parse(typeof(Color), someString);

 

(holy redundancy batman!) *shudder*.

So i’d love to be able to write something helpful like this:

class EnumUtilities<T> where T : enum

{

     public static T Parse(string enumMemberName)

     {

          return (T)Enum.Parse(typeof(T), enumMemberName);

     }

 

     //Returns true if there is a single member in this enum equal to “value”

     public bool IsMember(T value) { … }

 

     //Returns true if “value” can be represented as the union of any members of this enum

     public bool IsUnionOfMembers(T value) { … }

}

 

 

Color c = EnumUtilities<Color>.Parse(someString);

Debug.Assert(EnumUtilities<Color>.IsMember(someColorHandedToYouByAnother));

 

Seems reasonable right?  A useful way to get around the limitations of the built in enum type by using generics.  And, instead of the ugly non-typesafe redundant calls you need to do now you have a nice safe method that is checked at compile type to be ok.

But… no.

Of course, once again, i try to do this and i experience another cut.  “enum is not a valid constraint for a type parameter”.  Sigh…

We already allow you to contrain to reference types or value types.  So why not allow you to constrain to enums?  It’s just one of those missing things that really annoys you once you hit it.  Of course, there are workarounds.  You could do this:

class EnumUtilities<T> where T : struct

{

     static EnumUtilities

     {

          if (!typeof(T).IsSubclassOf(typeof(Enum)))

          {

              throw new ArgumentException(“The type parameters T must be an enum”);

          }

}

 

     public static T Parse(string enumMemberName)

     {

          return (T)Enum.Parse(typeof(T), enumMemberName);

     }

}

 

 

//Compiles fine and fails at runtime

int c = EnumUtilities<int>.Parse(someString);

 

I hate having things fail at runtime.  I’m a big fan of the compiler figuring this all out for me and telling me right when i compile.  But, nope.  Once again I have no choice but ugly workarounds and asking myself “why couldn’t this just be consistant?”


Comments (18)

  1. Eric says:

    Wouldn’t it be possible for the compiler to automatically generate a specific Parse method for each Enum, so that all of this is entirely unnecessary?

    i.e.

    enum Foo

    {

    FirstValue,

    SecondValue

    }

    would generate a Parse method that looks like this:

    public static Foo Parse(string enumMemberName)

    {

    return (Foo)Enum.Parse(typeof(Foo), enumMemberName);

    }

    Seems perfectly workable to me.

  2. AT says:

    I’ve commented out on this matter before:

    This is regarding Enum restriction on generics :

    http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=FDBK16420

    As well – there were suggestion about exactly the same issue at

    http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=FDBK14577

    Even more – using generics for enums was suggested by me at betaplace at 6/19/2004 as 320417672

    ;-))

    P.S> Cyrus, you have so many bugs in your code that it does not even compile 😉

    Instead of (!typeof(T).IsSubclassOf(typeof(Enum)) you can use

    (!typeof(T).IsEnum).

    As well – throwing exceptions in static constructor is not good.

    Personaly I preffer TryParse() aproach (BTW, also missing)

  3. Jim Argeropoulos says:

    Eric

    The first problem is that you cannot have free methods as above.

    The second is that you cannot write a generic function that handles all enum definitions, you would have to write a function for every enum that you wanted to use the technique with.

    Put the two together and you have a class that is ever changing and has very high coupling. Two bad things.

    Generics would be the way to go, but alas.

  4. Philip Rieck says:

    An enum constraint would be a good thing. I’m afraid that if it doesnt go in now, though adding possible constraints in the future may be too late.

    See also: http://philiprieck.com/blog/archive/2005/02/17/537.aspx

  5. Nick says:

    On defining delegates to properties… you actually can do it with some reflection magic… I talked about it a while ago on my blog:

    http://schweitn.blogspot.com/2004/03/wrapping-up-properties-with-delegates.html

  6. Sean Chase says:

    Agreed, great post Cyrus.

  7. Eric: That would absolutely be possible. But what about what happens when you get to a method that we didn’t think about? We can’t just jam a kajillion methods into Enum and hope that they suffice. So you want an extensible solution that will work in all cases for whatever the developer has in mind.

  8. Philip: Could you expand on this a little more. Why would it be too late if it didn’t go in now? If it was added in the future it could be used without breaking backwards compatability.

  9. Nick: In my original post i mentioned:

    "Of course, this isn’t allowed today because the argument to a delegate must be a method. But this always ends up getting in my way and i have to create nasty workarounds."

    Your solution would fall in the camp of what i would consider a "nasty workaround" 😉

    That’s my problem. There are always ways around C#’s shortcomings. But why do the shortcomings exist in the first place? Sometimes it really makes no sense to me.

  10. Nick says:

    Cyrus: Agreed… yet the workaround does exist… and it’s good for people to know about it I think. You work with what you’re given.

  11. Joe Duffy says:

    One of the glorious things about having a "unified" type system is how a) it isn’t really unified (e.g. the whole value vs. ref type bifurcation), b) that programmers are able, and even _need_, to predicate logic based on the lack of unification, and c) that C# seems to enjoy breaking this even further by treating various interesting special types in a special fashion.

    The more arbitrary rules in a complex system, the more complex and arbitrary the system becomes. I’m with you all the way on this one. Although it seems that this is an arbitrary restriction, while co- and contra-variance are additive features. (Yes, without them it’s a restriction, but given that this theoretically sound facet has never been part of the CLR type system, I don’t see it in the same light.)

    Nonetheless, luckily this is a C# thing (although, no–I haven’t read the CLI v2.0 to see whether we guarantee this to work in the future). You can hack up some IL to do this correctly:

    .method public hidebysig static !!T Parse<([mscorlib]System.Enum) T>(string s) cil managed

    {

    // Code size 27 (0x1b)

    .maxstack 2

    .locals init (!!T V_0)

    IL_0000: nop

    IL_0001: ldtoken !!T

    IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype

    [mscorlib]System.RuntimeTypeHandle)

    IL_000b: ldarg.0

    IL_000c: call object [mscorlib]System.Enum::Parse(class [mscorlib]System.Type,

    string)

    IL_0011: unbox.any !!T

    IL_0016: stloc.0

    IL_0017: br.s IL_0019

    IL_0019: ldloc.0

    IL_001a: ret

    } // end of method X::Parse

  12. Joe: Will that allow me to do:

    Parse<System.Enum>("someVal");

    or will it restrict it to only enums that aren’t System.Enum (which isn’t an enum).

    That’s one of the slight difference between the "sruct" constraint and the System.ValueType consrtaint. (it also related to how Nullable is treated)

  13. Joe Duffy says:

    Yes, you could cause this to fail at runtime by passing in System.Enum. (Although hopefully the developer would realize their error before doing it. ;)) But it seems that’s more appealing than successfully compiling with, say, anything outside of Enum’s hierarchy. But still I admit it isn’t perfect.

    I dislike the constraints as they are. They’re very arbitrary. If saying "only accept things which are a subclass of ValueType, but don’t accept ValueType itself" is valuable, then why wouldn’t "only accept things which are a subclass of Foo, but don’t accept Foo itself" also be valuable?

    In other words, unify the concept. Support one unambiguous means by which to constrain type arguments to subclasses. Then you can use it for ValueType, Enum, Foo, Bar, Baz, whatever.

    E.g.

    static T Parse<T>(string s) where T subclasses Enum

    {

    }

    Or whatever clever syntax can be thought of. Also note that whatever the solution is, I would find it particularly appealing if C# supported the keyword in the context of something like "is". The use of ":" to indicate "is" is particularly annoying to me. The syntax should have been:

    static T Parse<T>(string s) where T is ValueType

    {

    }

    And so on. Perhaps I’m too preachy, but I hate languages representing similar concepts in disparate ways depending on the context in which they are used. Now I also understand that you sometimes need to constrain arguments to types which _don’t_ fall into a certain hierarchy. E.g. for ref types. But why not "where T isnot ValueType" (you get the idea).

    Don’t even get me started on Nullable. Why is it that in my Haskell compiler I need to create my own Maybe<T> type (and distribute it in a runtime library) simply because somebody thought it wouldn’t be interesting to store a ref type in a Nullable<T> instance? What _harm_ would that do? Given that I work alongside the designers of the class, one would think it’s easy to get an answer. 😉

  14. duncan says:

    "I would find it particularly appealing if C# supported the keyword in the context of something like "is". The use of ":" to indicate "is" is particularly annoying to me."

    You and me both. However I suspect that it is now far far to late in C# history to ever change such a thing.

    I also feel the same way about the : for class extension and interface implementation, but maybe it’s just my java heritage showing.

  15. Philip Rieck says:

    Cyrus:

    I said "may be too late" not because of breaking changes – I mean "may be too late to stop us from swearing at it in the future".

    In the future, adding where T:enum would be the right solution. But what if that proves to be too difficult since enums are treated more like ints/structs than what they really are (enums). So the CLR/BCL/whatever teams get together and decide that they’ll add "IParseable" to all BCL/FCL enums and also to anything that allows parsing.

    Great, now you can use "where T:IParseable". However, since all the enums *I* wrote don’t implement that new interface, you can’t use them in your generic. so you’re still stuck with a runtime solution. Damn.

    What if they *do* add the ability to put the "where T:enum" constraint on there? Well, then they’ll have to continue to treat enums as structs when "where T:struct" is there. So in the future, we’re stuck with a slightly ambiguous constraint for struct and having to check – again at runtime – if it’s a struct or an enum when we only want structs.

    C# is going through a big shift now. Let’s do it right and have to bear less pressure from the sins of the past later. Obviously we have to deal with code and types written using c#1.0. But the more we do for 2.0 is less we have to deal with when they deisign 3.0

  16. Mishel says:

    Your site is realy very interesting. http://www.bignews.com