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
{
...
}
3

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

8 Comments

This does work for delegates, except that it won't exclude Delegate itself.
@bsnote: That doesn't prevent you from passing int or DateTime at compile time.
This is seriously fantastic, and no, @bsnote, runtime checking is never better than compile time checking, for cases where compile time checking is possible.
@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.
|
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.)

28 Comments

The CLR does support it, at least according to my understanding of page 166 f the spec.
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.)
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).
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?
|
17
public static T GetEnum<T>(this string description) where T : struct
{
    return (T)Enum.Parse(typeof(T), description);
}

Does it answer your question?

4 Comments

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.
You can do a runtime check if you like. I did: !typeof(T).IsEnum
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.
I do something like this on the first line: if (!typeof(T).IsEnum) { throw new InvalidOperationException("MethodBlah requires an enum!"); }
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
{
    ...
}

2 Comments

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...
@WaiHaLee thanks. link fixed
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

Comments

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.