更新:有关C#解决方法,请参阅此问题的底部。
你好,
考虑以下扩展方法:
public static bool HasFlags<T>(this T value, T flags)
where T : System.Enum
{
// ...
}
正如您所知,这将在编译时抛出错误,因为通常不允许类从System.Enum
继承。问题是使用enum
关键字指定的任何枚举实际上都是从System.Enum
继承的,因此上述代码是将扩展方法限制为枚举的理想方法。
现在显而易见的解决方法是使用Enum
代替T
,但是你失去了泛型类型的好处:
MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);
上面的代码会使用泛型类型抛出编译时错误,而它只能使用Enum
类型抛出运行时错误(如果我实现它就会这样做。)
是否有可用于关闭约束检查的编译器选项,还是有其他一些很好的方法可以做到这一点?
在提出建议之前,我想说我不会使用where T : struct
或其他类似内容,因为那时你可以做123.HasFlags(456)
之类的奇怪内容。
我很难理解为什么会出现这个错误......这与使用where T : System.Object
时遇到的问题相同,但为此你有where T : class
...为什么没有where T : enum
?
C#workaround
Jon Skeet已经开始研究一个库,该库将具有约束的类编译为
IEnumConstraint
,然后将其替换为System.Enum
后构建。我相信,这是目前最接近解决这个问题的人。请参阅:
- 代码项目:http://code.google.com/p/unconstrained-melody/
- 博客条目:http://msmvps.com/blogs/jon_skeet/archive/2009/09/10/generic-constraints-for-enums-and-delegates.aspx
如果这种解决方法不可行,则必须将库编写为C ++ / CLI代码,这不会限制可用于泛型类型约束的内容(请参阅下面的答案中的代码。)
答案 0 :(得分:10)
编辑:现在可以通过ildasm / ilasm支持此库:UnconstrainedMelody。
C#团队的成员之前已经表示他们喜欢能够支持where T : Enum
和where T : Delegate
,但它从未有过足够高的优先级。 (我不确定首先有限制的原因是什么,不可否认......)
C#中最实用的解决方法是:
public static bool HasFlags<T>(this T value, T flags) where T : struct
{
if (!(value is Enum))
{
throw new ArgumentException();
}
// ...
}
失去编译时检查“enum-ness”,但检查你在两个地方都使用相同的类型。当然,它也有检查的执行时间惩罚。在第一次调用之后,您可以通过在实现中使用通用嵌套类型来避免执行时间损失,该实现会在静态构造函数中抛出异常:
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)
{
...
}
}
正如Greco所提到的,您可以在C ++ / CLI中编写该方法,然后从C#引用类库作为另一种选择。
答案 1 :(得分:3)
实际上,这可能是一个丑陋的伎俩。 但是,它不能用于扩展方法。
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")
如果您愿意,可以将Enums<Temp>
私有构造函数和公共嵌套抽象继承类Temp
作为Enum
,以防止非枚举的继承版本。
答案 2 :(得分:2)
我无法抗拒参加C ++的解决方案,而且自从我开始工作以来,我想我会和其余的人一起分享它!
这是C ++代码(我的C ++非常生疏,所以请指出任何错误,特别是如何定义参数):
#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;
}
};
}
}
用于测试的C#代码(控制台应用程序):
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);
}
}
}
答案 3 :(得分:1)
您可以使用IL Weaving和ExtraConstraints
来实现这一目标允许您编写此代码
public static bool HasFlags<[EnumConstraint] T>(this T value, T flags)
{
// ...
}
汇编了什么
public static bool HasFlags<T>(this T value, T flags)
where T : System.Enum
{
// ...
}