Enum type constraints in C#
C#.NetEnumsC# Problem Overview
> 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
{
...
}
C# Solutions
Solution 1 - C#
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.
Solution 2 - C#
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.)
Solution 3 - C#
public static T GetEnum<T>(this string description) where T : struct
{
return (T)Enum.Parse(typeof(T), description);
}
Does it answer your question?
Solution 4 - C#
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
{
...
}
Solution 5 - C#
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
Solution 6 - C#
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).