Update: See the bottom of this question for a C# workaround.
Hi there,
Consider the following extension method:
public static bool HasFlags<T>(this T value, T flags)
where T : System.Enum
{
// ...
}
This will, as you may know, throw an error at compile-time, since a class is not normally allowed to inherit from System.Enum
. The problem is that any enumeration specified using the enum
keyword does in fact inherit from System.Enum
, so the above code would be the ideal way to limit an extension method to enumerations only.
Now the obvious work-around here is to use Enum
instead of T
, but then you lose the benefits of generic types:
MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);
The above code would throw a compile-time error using generic types, while it can only throw a runtime error using the Enum
type (if I implement it to do so.)
Are there any compiler options that can be used to turn off the constraint check, or is there some other nifty way to do this?
Before it is suggested, I would like to say that I will not be using where T : struct
or some such, since then you'd be able to do weird stuff like 123.HasFlags(456)
.
I'm stumped as to why this error exists at all... It's the same problem you'd get using where T : System.Object
, but for that you have where T : class
... Why is there no where T : enum
?
C# workaround
Jon Skeet has started work on a library that compiles classes with a constraint to an
IEnumConstraint
, which is then replaced withSystem.Enum
post-build. This is, I believe, the closest one can get to working around this issue at this time.See:
- Code project: http://code.google.com/p/unconstrained-melody/
- Blog entry: http://msmvps.com/blogs/jon_skeet/archive/2009/09/10/generic-constraints-for-enums-and-delegates.aspx
If this workaround is unfeasible, you will have to write your library as C++/CLI code, which does not limit what can be used for generic type constraints (see the code in my answer below.)
On a technical level, there's nothing wrong with using an enum as the type used in a generic type.
Multiple interface constraints can be specified. The constraining interface can also be generic.
A type constraint on a generic type parameter indicates a requirement that a type must fulfill in order to be accepted as a type argument for that type parameter. (For example, it might have to be a given class type or a subtype of that class type, or it might have to implement a given interface.)
EDIT: A library is now available supporting this via ildasm/ilasm: UnconstrainedMelody.
Members of the C# team have previously said they'd like to be able to support where T : Enum
and where T : Delegate
, but that it's never been a high enough priority. (I'm not sure what the reasoning is for having the restriction in the first place, admittedly...)
The most practical workaround in C# is:
public static bool HasFlags<T>(this T value, T flags) where T : struct
{
if (!(value is Enum))
{
throw new ArgumentException();
}
// ...
}
That loses compile-time checking for the "enum-ness" but keeps the check that you're using the same type in both places. It has the execution-time penalty of the check as well, of course. You can avoid that execution-time penalty after the first call by using a generic nested type for the implementation which throws the exception in a static constructor:
public static bool HasFlags<T>(this T value, T flags) where T : struct
{
if (!(value is Enum))
{
throw new ArgumentException();
}
return EnumHelper<T>.HasFlags(value, flags);
}
private class EnumHelper<T> where T : struct
{
static EnumHelper()
{
if (!typeof(Enum).IsAssignableFrom(typeof(T))
{
throw new InvalidOperationException(); // Or something similar
}
}
internal static HasFlags(T value, T flags)
{
...
}
}
As Greco mentions, you could write the method in C++/CLI and then reference the class library from C# as another option.
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.
I couldn't resist having a go at the C++ work-around, and since I got it to work I figured I'd share it with the rest of ya!
Here's the C++ code (my C++ is very rusty so please point out any errors, in particular how the arguments are defined):
#include "stdafx.h"
using namespace System;
using namespace System::Runtime::CompilerServices;
namespace Blixt
{
namespace Utilities
{
[Extension]
public ref class EnumUtility abstract sealed
{
public:
generic <typename T> where T : value class, Enum
[Extension]
static bool HasFlags(T value, T flags)
{
__int64 mask = Convert::ToInt64(flags);
return (Convert::ToInt64(value) & mask) == mask;
}
};
}
}
And the C# code for testing (console application):
using System;
using Blixt.Utilities;
namespace Blixt.Playground
{
[Flags]
public enum Colors : byte
{
Black = 0,
Red = 1,
Green = 2,
Blue = 4
}
[Flags]
public enum Tastes : byte
{
Nothing = 0,
Sour = 1,
Sweet = 2,
Bitter = 4,
Salty = 8
}
class Program
{
static void Main(string[] args)
{
Colors c = Colors.Blue | Colors.Red;
Console.WriteLine("Green and blue? {0}", c.HasFlags(Colors.Green | Colors.Red));
Console.WriteLine("Blue? {0}", c.HasFlags(Colors.Blue));
Console.WriteLine("Green? {0}", c.HasFlags(Colors.Green));
Console.WriteLine("Red and blue? {0}", c.HasFlags(Colors.Red | Colors.Blue));
// Compilation error:
//Console.WriteLine("Sour? {0}", c.HasFlags(Tastes.Sour));
Console.WriteLine("Press any key to exit...");
Console.ReadKey(true);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With