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
{
...
}
Community
  • 1
  • 1
Taylor Leese
  • 51,004
  • 28
  • 112
  • 141

6 Answers6

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.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    This does work for delegates, except that it won't exclude `Delegate` itself. – SLaks Sep 13 '09 at 22:01
  • 2
    The more elegant way: http://stackoverflow.com/questions/1331739/enum-type-constraints-in-c/5995541#5995541 – Andrei Sedoi May 13 '11 at 17:21
  • 4
    @bsnote: That doesn't prevent you from passing `int` or `DateTime` at compile time. – SLaks May 13 '11 at 17:41
  • 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. – Andrei Sedoi May 21 '11 at 16:08
  • 25
    This is seriously fantastic, and no, @bsnote, runtime checking is never better than compile time checking, for cases where compile time checking is possible. – cwharris Oct 12 '11 at 21:00
  • 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 { public static TEnum Parse(string name) where TEnum : Temp { return (TEnum)Enum.Parse(typeof(TEnum), name); } } public abstract class Enums : Enums { }` – hypehuman Sep 17 '15 at 21:35
  • 2
    @hypehuman: To prevent you from using `Enums` (except `object` and `ValueType`). Those are the only types that meet `: class` and have subtypes that meet `: struct`. – SLaks Sep 18 '15 at 01:56
  • Actually it is possible in C++/CLI without any tricks: `generic where T : Enum public ref class CClassName ` – Tobias Knauss Feb 04 '18 at 09:39
96

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

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 5
    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. – SLaks Aug 26 '09 at 01:09
  • 19
    The CLR does support it, at least according to my understanding of page 166 f the spec. – SLaks Aug 26 '09 at 01:10
  • 2
    Funny you mentioned delegate constraints because I'd love to see those as well. – Taylor Leese Aug 26 '09 at 01:19
  • 2
    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. – Eric Lippert Aug 26 '09 at 04:32
  • 2
    Let's put it this way -- is there a way to do it with ILASM? – Eric Lippert Aug 26 '09 at 04:33
  • 1
    As I understand (I'm too lazy to try it & I don't write IL), yes: `<.ctor (class [mscorlib]System.Enum) T>` – SLaks Aug 26 '09 at 05:21
  • 42
    Somehow I missed this post before. In another question, this blog post has been linked: [The Case of the Missing Generic (Parse Method)](http://weblogs.asp.net/kennykerr/The-Case-of-the-Missing-Generic-_2800_Parse-Method_2900_) 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.) – Jon Skeet Sep 10 '09 at 08:52
  • 14
    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 (http://www.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 (http://code.google.com/p/unconstrained-melody/). – RCIX Nov 14 '09 at 22:58
  • 44
    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? – Eric Lippert Nov 15 '09 at 08:28
  • 12
    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. – SecurityMatt Aug 24 '12 at 11:43
  • @JonSkeet - Your link is broken; I think you meant [The Case of the Missing Generic (Parse Method)](http://goo.gl/LpiQk) – Justin Morgan - On strike Apr 25 '13 at 20:29
  • @JustinMorgan: Yup, that's the one. Oops. – Jon Skeet Apr 25 '13 at 20:30
  • check this answer http://stackoverflow.com/a/38410351/4009642 – Ahmed Fwela Jul 18 '16 at 01:25
  • Where can I make an official request for this feature? – Kyle Delaney Oct 11 '17 at 02:44
  • @KyleDelaney: Github. – Eric Lippert Oct 11 '17 at 02:46
  • What, do I report it as an issue? – Kyle Delaney Oct 11 '17 at 02:53
  • 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. – Tobias Knauss Feb 04 '18 at 09:32
  • 2
    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)? – Tom Feb 13 '18 at 13:32
  • @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? – Eric Lippert Feb 13 '18 at 15:35
  • 1
    @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. – Eric Lippert Feb 13 '18 at 15:44
  • 2
    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"? – Tom Feb 14 '18 at 00:34
  • 5
    @Tom: Well, let's think about it. Suppose we have `void M(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. – Eric Lippert Feb 14 '18 at 00:45
  • 1
    @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... – Eric Lippert Feb 14 '18 at 00:47
  • 1
    @Tom: And then someone says well what about `class C { public virtual void M(U u) where U : T { } } class B : C { public override void M(U u) { } }` and now what? Does this behave *exactly* as though there was a constraint of `System.Enum` on `U` in `B.M`, or is this subtle different than if we'd written `where U : System.Enum` instead of `where U : T`, and the fight begins again... – Eric Lippert Feb 14 '18 at 00:50
  • 1
    @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. – Eric Lippert Feb 14 '18 at 00:52
  • 1
    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! – Tom Feb 15 '18 at 11:01
  • @Tom: See also the design considerations discussed in [Proposal: support an enum constraint on generic type parameters](https://github.com/dotnet/roslyn/issues/262). – Marc Sigrist Mar 06 '18 at 20:42
  • 13
    FWIW, [C# 7.3 adds enum constraints](https://learn.microsoft.com/en-us/visualstudio/releasenotes/vs2017-preview-relnotes#csharp) – Sören Kuklau Apr 10 '18 at 04:57
17
public static T GetEnum<T>(this string description) where T : struct
{
    return (T)Enum.Parse(typeof(T), description);
}

Does it answer your question?

Andrei Sedoi
  • 1,534
  • 1
  • 15
  • 28
  • 18
    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. – dmihailescu Jul 01 '11 at 14:27
  • 2
    You can do a runtime check if you like. I did: !typeof(T).IsEnum – Fabio Milheiro Sep 11 '13 at 10:53
  • 3
    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. – Mike Fuchs Sep 08 '14 at 16:33
  • I do something like this on the first line: `if (!typeof(T).IsEnum) { throw new InvalidOperationException("MethodBlah requires an enum!"); }` – BrainSlugs83 Apr 26 '16 at 22:39
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
{
    ...
}
ErikE
  • 48,881
  • 23
  • 151
  • 196
Simon
  • 33,714
  • 21
  • 133
  • 202
  • 1
    Go @Simon, your link to https://github.com/SimonCropp/ExtraConstraints is dead. Looking at the history, should it point to https://github.com/Fody/ExtraConstraints instead? It said the last commit was by SimonCropp 1 month ago... – Wai Ha Lee Mar 15 '15 at 12:57
  • 2
    @WaiHaLee thanks. link fixed – Simon Mar 16 '15 at 00:08
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
Community
  • 1
  • 1
Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
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).

Doug McClean
  • 14,265
  • 6
  • 48
  • 70