Enum type constraints in C# [duplicate]
Asked Answered
G

6

162

Possible Duplicate:
Anyone know a good workaround for the lack of an enum generic constraint?

What is the reason behind C# not allowing type constraints on Enum's? I'm sure there is a method behind the madness, but I'd like to understand why it's not possible.

Below is what I would like to be able to do (in theory).

public static T GetEnum<T>(this string description) where T : Enum
{
...
}
Gosport answered 26/8, 2009 at 0:26 Comment(3)
See also #1404577Gosport
To anyone wondering this is coming in C# 7.3: learn.microsoft.com/en-us/visualstudio/releasenotes/….Maida
Use where T : struct, Enum as restrictionAkim
F
97

This is an occasionally requested feature.

As I'm fond of pointing out, ALL features are unimplemented until someone designs, specs, implements, tests, documents and ships the feature. So far, no one has done that for this one. There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.

The CLR doesn't support it, so in order to make it work we'd need to do runtime work in addition to the language work. (see answer comments)

I can see that there are a few decent usage cases, but none of them are so compelling that we'd do this work rather than one of the hundreds of other features that are much more frequently requested, or have more compelling and farther-reaching usage cases. (If we're going to muck with this code, I'd personally prioritize delegate constraints way, way above enum constraints.)

Feeder answered 26/8, 2009 at 0:52 Comment(28)
I don't see how that's true. On the contrary, the ground state would be to allow it, and the restriction didn't exist until someone made the CS0702 compiler errror.Mikamikado
The CLR does support it, at least according to my understanding of page 166 f the spec.Mikamikado
Funny you mentioned delegate constraints because I'd love to see those as well.Gosport
Last time I overheard a conversation about this with the CLR guys they said that they didn't have the gear in place for it, but it is entirely possible that I was misunderstanding what they were talking about.Feeder
Let's put it this way -- is there a way to do it with ILASM?Feeder
As I understand (I'm too lazy to try it & I don't write IL), yes: <.ctor (class [mscorlib]System.Enum) T>Mikamikado
Somehow I missed this post before. In another question, this blog post has been linked: The Case of the Missing Generic (Parse Method) That suggests that it is available in IL. I seem to remember trying it before and it working, although that might have been for Delegate. I'd be happy to experiment with this a bit more (heck, a lot more) if it might mean the restrictions being removed from C# 5. (I assume C# 4 is locked down now.)Intertwist
I'm confused as to why you state the CLR does not support Enum constraints, as it does both according to page 166/167 of the CLI ECMA document (ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf) (that is, unless the CLR does not conform to the CLI spec) and the work of Jon Skeet in Unconstrained Melody (code.google.com/p/unconstrained-melody).Fabron
Well then, ask yourself what's more likely? That the spec is wrong and Jon's implementation is fictitious, or that I'm misremembering or misinterpreting a conversation I had with the CLR guys about this over a year ago?Feeder
The feature isn't unimplemented - it's disallowed. SLaks' solution below isn't a way of implementing the feature - it's a way of removing the CS0702 error. Since constraining a generic parameter to inheriting from an Enum is reasonable and clearly works, adding a rule to specifically prohibit it seems bizarre.Teamwork
@JonSkeet - Your link is broken; I think you meant The Case of the Missing Generic (Parse Method)Frog
@JustinMorgan: Yup, that's the one. Oops.Intertwist
check this answer https://mcmap.net/q/22288/-how-do-i-convert-an-enum-between-string-list-of-flags-enum-with-flagsWakerly
Where can I make an official request for this feature?Gainor
@KyleDelaney: Github.Feeder
What, do I report it as an issue?Gainor
Not only that the CLR supports it, but it is even possible in C++/CLI. I ported a class to C# and wondered why it isn't possible there.Woodpile
Hey @EricLippert, as this is still not answered quite a few years later, can you please comment on this specific point: Is this really a feature one would need to design, spec, implement, test etc? Isn't the feature already there (constraints on generic types)? Why would the constraint where T : Enum require special treatment (i.e. anything that would need to be designed, specked etc specifically for this constraint that is not covered by the generic type constraints feature)?Taxation
@Tom: You are suggesting that there exist features that do not need to be designed, specified and tested? How would you know that the feature had been implemented correctly if it was not designed, specified and tested?Feeder
@Tom: I suspect you are thinking about "design" too narrowly here. The question facing the design team is not merely "what should the syntax be?" As you rightly note, that will take five minutes. The questions facing the design team are "does the proposed feature introduce any possibility of breaking change in any existing program?" and "does the feature make any feature on our proposed future features list harder?" and a great many similar questions. Design of even small features has to be undertaken carefully if you want your language to survive for decades.Feeder
Thanks for your answer, @EricLippert! Let me clarify what I mean, and what I'll be happy if you can answer: The feature we're talking about is already part of the language: given a non-sealed class C one can use the type constraint where T : C. The syntax is clear, and the compiler and runtime support it. But a conscious decision was made to forbid where T : System.Enum. So my question is what's so special about System.Enum? Why can't it be treated just like any other class? What is it that would make treating System.Enum like any other class constitute a "new feature"?Taxation
@Tom: Well, let's think about it. Suppose we have void M<T>(T t) where T : System.Enum{} Question number one: is this legal? M((System.Enum)null); It sure looks like it ought to be legal, it sure looks like type inference should infer that T is System.Enum, and it sure looks like that constraint was met. And since System.Enum is a reference type, it's perfectly legal for it to be null. Do you want this program to be legal? And now we have a design problem that we can argue about, and now you know how I spent about seven years of my life.Feeder
@Tom: Because I guarantee you that half the design team will say well yeah, obviously that should be legal because it meets the letter of the law, and the other half will say that no obviously it should be illegal because it violate the intention that the type be a specific enum, and then someone says wait, what does the CLR verifier do in this other obscure corner case, and then we go investigate that for a while...Feeder
@Tom: And then someone says well what about class C<T> { public virtual void M<U>(U u) where U : T { } } class B : C<System.Enum> { public override void M<U>(U u) { } } and now what? Does this behave exactly as though there was a constraint of System.Enum on U in B.M<U>, or is this subtle different than if we'd written where U : System.Enum instead of where U : T, and the fight begins again...Feeder
@Tom: ... and then you discover that the verifier has special rules for that case, and the C# compiler is required to generate box/unbox pairs arount usages of u, and the jitter doesn't generate efficient code for them, and does that affect the design, because we don't want users to be tricked into thinking they're writing efficient code when they're not, and the fight begins AGAIN. Summing up: there are no simple features in the world of generic type systems. Every one of them has to be carefully thought through on its merits.Feeder
Thanks for the detailed explanations, @EricLippert. I think it makes sense to avoid special-casing in such cases. The language enables where T : SomeClass constraints, then why treat System.Enum differently? Some users might complain about the constraint allowing or not allowing System.Enum itself, and while they may have good arguments I would argue in the absence of a very clear reason to do otherwise one should "meet the letter of the law", and avoid special cases and extra constraints. In any case, I understand the complexities involved better now. Thanks again!Taxation
@Tom: See also the design considerations discussed in Proposal: support an enum constraint on generic type parameters.Gerund
FWIW, C# 7.3 adds enum constraintsMinetta
M
151

Actually, it is possible, with an ugly trick. However, it cannot be used for extension methods.

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.Parse<DateTimeKind>("Local")

If you want to, you can give Enums<Temp> a private constructor and a public nested abstract inherited class with Temp as Enum, to prevent inherited versions for non-enums.

Note that you can't use this trick to make extension methods.

Mikamikado answered 13/9, 2009 at 2:14 Comment(8)
This does work for delegates, except that it won't exclude Delegate itself.Mikamikado
The more elegant way: https://mcmap.net/q/22283/-enum-type-constraints-in-c-duplicate/…Jamey
@bsnote: That doesn't prevent you from passing int or DateTime at compile time.Mikamikado
In case of int or DateTime an exception will be thrown indicating that there is an incorrect logic somewhere in code. In my opinion in theory you are right, but practically my approach is more useful.Jamey
This is seriously fantastic, and no, @bsnote, runtime checking is never better than compile time checking, for cases where compile time checking is possible.Semibreve
Why specify "class" and "struct" constraints? If I remove those, it still works (ie. gives me a compile error for non-enum type x: "The type 'x' must be convertible to 'System.Enum' in order to..."). public abstract class Enums<Temp> { public static TEnum Parse<TEnum>(string name) where TEnum : Temp { return (TEnum)Enum.Parse(typeof(TEnum), name); } } public abstract class Enums : Enums<Enum> { }Diandrous
@hypehuman: To prevent you from using Enums<SomeOtherType> (except object and ValueType). Those are the only types that meet : class and have subtypes that meet : struct.Mikamikado
Actually it is possible in C++/CLI without any tricks: generic<class T> where T : Enum public ref class CClassName Woodpile
F
97

This is an occasionally requested feature.

As I'm fond of pointing out, ALL features are unimplemented until someone designs, specs, implements, tests, documents and ships the feature. So far, no one has done that for this one. There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.

The CLR doesn't support it, so in order to make it work we'd need to do runtime work in addition to the language work. (see answer comments)

I can see that there are a few decent usage cases, but none of them are so compelling that we'd do this work rather than one of the hundreds of other features that are much more frequently requested, or have more compelling and farther-reaching usage cases. (If we're going to muck with this code, I'd personally prioritize delegate constraints way, way above enum constraints.)

Feeder answered 26/8, 2009 at 0:52 Comment(28)
I don't see how that's true. On the contrary, the ground state would be to allow it, and the restriction didn't exist until someone made the CS0702 compiler errror.Mikamikado
The CLR does support it, at least according to my understanding of page 166 f the spec.Mikamikado
Funny you mentioned delegate constraints because I'd love to see those as well.Gosport
Last time I overheard a conversation about this with the CLR guys they said that they didn't have the gear in place for it, but it is entirely possible that I was misunderstanding what they were talking about.Feeder
Let's put it this way -- is there a way to do it with ILASM?Feeder
As I understand (I'm too lazy to try it & I don't write IL), yes: <.ctor (class [mscorlib]System.Enum) T>Mikamikado
Somehow I missed this post before. In another question, this blog post has been linked: The Case of the Missing Generic (Parse Method) That suggests that it is available in IL. I seem to remember trying it before and it working, although that might have been for Delegate. I'd be happy to experiment with this a bit more (heck, a lot more) if it might mean the restrictions being removed from C# 5. (I assume C# 4 is locked down now.)Intertwist
I'm confused as to why you state the CLR does not support Enum constraints, as it does both according to page 166/167 of the CLI ECMA document (ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf) (that is, unless the CLR does not conform to the CLI spec) and the work of Jon Skeet in Unconstrained Melody (code.google.com/p/unconstrained-melody).Fabron
Well then, ask yourself what's more likely? That the spec is wrong and Jon's implementation is fictitious, or that I'm misremembering or misinterpreting a conversation I had with the CLR guys about this over a year ago?Feeder
The feature isn't unimplemented - it's disallowed. SLaks' solution below isn't a way of implementing the feature - it's a way of removing the CS0702 error. Since constraining a generic parameter to inheriting from an Enum is reasonable and clearly works, adding a rule to specifically prohibit it seems bizarre.Teamwork
@JonSkeet - Your link is broken; I think you meant The Case of the Missing Generic (Parse Method)Frog
@JustinMorgan: Yup, that's the one. Oops.Intertwist
check this answer https://mcmap.net/q/22288/-how-do-i-convert-an-enum-between-string-list-of-flags-enum-with-flagsWakerly
Where can I make an official request for this feature?Gainor
@KyleDelaney: Github.Feeder
What, do I report it as an issue?Gainor
Not only that the CLR supports it, but it is even possible in C++/CLI. I ported a class to C# and wondered why it isn't possible there.Woodpile
Hey @EricLippert, as this is still not answered quite a few years later, can you please comment on this specific point: Is this really a feature one would need to design, spec, implement, test etc? Isn't the feature already there (constraints on generic types)? Why would the constraint where T : Enum require special treatment (i.e. anything that would need to be designed, specked etc specifically for this constraint that is not covered by the generic type constraints feature)?Taxation
@Tom: You are suggesting that there exist features that do not need to be designed, specified and tested? How would you know that the feature had been implemented correctly if it was not designed, specified and tested?Feeder
@Tom: I suspect you are thinking about "design" too narrowly here. The question facing the design team is not merely "what should the syntax be?" As you rightly note, that will take five minutes. The questions facing the design team are "does the proposed feature introduce any possibility of breaking change in any existing program?" and "does the feature make any feature on our proposed future features list harder?" and a great many similar questions. Design of even small features has to be undertaken carefully if you want your language to survive for decades.Feeder
Thanks for your answer, @EricLippert! Let me clarify what I mean, and what I'll be happy if you can answer: The feature we're talking about is already part of the language: given a non-sealed class C one can use the type constraint where T : C. The syntax is clear, and the compiler and runtime support it. But a conscious decision was made to forbid where T : System.Enum. So my question is what's so special about System.Enum? Why can't it be treated just like any other class? What is it that would make treating System.Enum like any other class constitute a "new feature"?Taxation
@Tom: Well, let's think about it. Suppose we have void M<T>(T t) where T : System.Enum{} Question number one: is this legal? M((System.Enum)null); It sure looks like it ought to be legal, it sure looks like type inference should infer that T is System.Enum, and it sure looks like that constraint was met. And since System.Enum is a reference type, it's perfectly legal for it to be null. Do you want this program to be legal? And now we have a design problem that we can argue about, and now you know how I spent about seven years of my life.Feeder
@Tom: Because I guarantee you that half the design team will say well yeah, obviously that should be legal because it meets the letter of the law, and the other half will say that no obviously it should be illegal because it violate the intention that the type be a specific enum, and then someone says wait, what does the CLR verifier do in this other obscure corner case, and then we go investigate that for a while...Feeder
@Tom: And then someone says well what about class C<T> { public virtual void M<U>(U u) where U : T { } } class B : C<System.Enum> { public override void M<U>(U u) { } } and now what? Does this behave exactly as though there was a constraint of System.Enum on U in B.M<U>, or is this subtle different than if we'd written where U : System.Enum instead of where U : T, and the fight begins again...Feeder
@Tom: ... and then you discover that the verifier has special rules for that case, and the C# compiler is required to generate box/unbox pairs arount usages of u, and the jitter doesn't generate efficient code for them, and does that affect the design, because we don't want users to be tricked into thinking they're writing efficient code when they're not, and the fight begins AGAIN. Summing up: there are no simple features in the world of generic type systems. Every one of them has to be carefully thought through on its merits.Feeder
Thanks for the detailed explanations, @EricLippert. I think it makes sense to avoid special-casing in such cases. The language enables where T : SomeClass constraints, then why treat System.Enum differently? Some users might complain about the constraint allowing or not allowing System.Enum itself, and while they may have good arguments I would argue in the absence of a very clear reason to do otherwise one should "meet the letter of the law", and avoid special cases and extra constraints. In any case, I understand the complexities involved better now. Thanks again!Taxation
@Tom: See also the design considerations discussed in Proposal: support an enum constraint on generic type parameters.Gerund
FWIW, C# 7.3 adds enum constraintsMinetta
J
17
public static T GetEnum<T>(this string description) where T : struct
{
    return (T)Enum.Parse(typeof(T), description);
}

Does it answer your question?

Jamey answered 13/5, 2011 at 17:21 Comment(4)
Nope! The question was about narrowing the the type parameter T to Enum. 'struct' is too broad and includes int, float, double, DateTime and other types that can be defined even by the user as structs.Yip
You can do a runtime check if you like. I did: !typeof(T).IsEnumMidis
Yep! Just what I needed to write my generic method. Restricting further to enum directly would be nice, but not absolutely necessary in most cases I presume.Sesquiplane
I do something like this on the first line: if (!typeof(T).IsEnum) { throw new InvalidOperationException("MethodBlah requires an enum!"); }Mansour
Z
8

IL Weaving using ExtraConstraints

Your Code

public static T GetEnum<[EnumConstraint] T>(this string description)
{
    ...
}

What gets compiled

public static T GetEnum<T>(this string description) where T : Enum
{
    ...
}
Zantos answered 20/7, 2012 at 7:9 Comment(2)
Go @Simon, your link to github.com/SimonCropp/ExtraConstraints is dead. Looking at the history, should it point to github.com/Fody/ExtraConstraints instead? It said the last commit was by SimonCropp 1 month ago...Gillyflower
@WaiHaLee thanks. link fixedZantos
P
4

Here's a VB.NET version of SLaks excellent ugly trick, with Imports as a "typedef": (Type inference works as expected, but you can't get extension methods.)

'Base namespace "EnumConstraint"
Imports Enums = EnumConstraint.Enums(Of System.Enum)

Public NotInheritable Class Enums(Of Temp As Class)
Private Sub New()
End Sub

Public Shared Function Parse(Of TEnum As {Temp, Structure})(ByVal Name As String) As TEnum
    Return DirectCast([Enum].Parse(GetType(TEnum), Name), TEnum)
End Function

Public Shared Function IsDefined(Of TEnum As {Temp, Structure})(ByVal Value As TEnum) As Boolean
    Return [Enum].IsDefined(GetType(TEnum), Value)
End Function

Public Shared Function HasFlags(Of TEnum As {Temp, Structure})(ByVal Value As TEnum, ByVal Flags As TEnum) As Boolean
    Dim flags64 As Long = Convert.ToInt64(Flags)
    Return (Convert.ToInt64(Value) And flags64) = flags64
End Function

End Class

Module Module1

Sub Main()

    Dim k = Enums.Parse(Of DateTimeKind)("Local")
    Console.WriteLine("{0} = {1}", k, CInt(k))
    Console.WriteLine("IsDefined({0}) = {1}", k, Enums.IsDefined(k))
    k = DirectCast(k * 2, DateTimeKind)
    Console.WriteLine("IsDefined({0}) = {1}", k, Enums.IsDefined(k))

    Console.WriteLine(" {0} same as {1} Or {2}: {3} ", IO.FileAccess.ReadWrite, IO.FileAccess.Read, IO.FileAccess.Write, _
                      Enums.HasFlags(IO.FileAccess.ReadWrite, IO.FileAccess.Read Or IO.FileAccess.Write))

    ' These fail to compile as expected:
    'Console.WriteLine(Enums.HasFlags(IO.FileAccess.ReadWrite, IO.FileOptions.RandomAccess))
    'Console.WriteLine(Enums.HasFlags(Of IO.FileAccess)(IO.FileAccess.ReadWrite, IO.FileOptions.RandomAccess))

    If Debugger.IsAttached Then _
        Console.ReadLine()
End Sub

End Module

Output:

Local = 2
IsDefined(Local) = True
IsDefined(4) = False
 ReadWrite same as Read Or Write: True
Presumptive answered 31/5, 2012 at 17:4 Comment(0)
V
2

One quirky thing here is that there are a fair number of generic Enum methods you might want to write whose implementation depends on the "base" type of the enumeration.

By the "base" type of an enumeration, E, I mean the type in the System namespace whose name is the same as the name of the member of System.TypeCode enumeration obtained by calling System.Type.GetTypeCode(System.Type) for the type E. If the enumeration was declared in C#, this is the same type that it was declared to "inherit" from (I'm not sure what this is officially called in the spec). For example, the base type of the Animal enumeration below is System.Byte:

public enum Animal : byte
{
    Moose,
    Squirrel
}

It's possible to write such methods using switch statements, but it sure is ugly, you can't get strongly typed parameters or return types whose type is the base type of the enumeration, and you have to either repeat the metadata lookup or do some caching (e.g. in the static constructor for the generic type containing the method).

Vikkivikky answered 10/5, 2010 at 17:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.