考虑到这一点:
[Flags]
public enum MyEnum {
One = 1,
Two = 2,
Four = 4,
Eight = 8
}
public static class FlagsHelper
{
public static bool Contains(this MyEnum keys, MyEnum flag)
{
return (keys & flag) != 0;
}
}
是否可以编写适用于任何enum
而不只是MyEnum
的包含的通用版本?
修改
在阅读完答案后,这将是我的版本:
public static bool Contains(this Enum keys, Enum flag)
{
ulong keysVal = Convert.ToUInt64(keys);
ulong flagVal = Convert.ToUInt64(flag);
return (keysVal & flagVal) == flagVal;
}
刚刚意识到检查我检查的方式(return (keys & flag) != 0;
)是一个坏主意,因为flag
参数可能实际上是几个标志,而常识事项只有在{时才返回true {1}}包含所有这些内容。此外,我不会检查空值或甚至确保它们是相同的类型。我可能希望使用不同的类型。
答案 0 :(得分:53)
我将这种方法基于一堆SO&谷歌搜索,并使用反射器来查看MS为.NET 4 HasFlags方法做了什么。
public static class EnumExt
{
/// <summary>
/// Check to see if a flags enumeration has a specific flag set.
/// </summary>
/// <param name="variable">Flags enumeration to check</param>
/// <param name="value">Flag to check for</param>
/// <returns></returns>
public static bool HasFlag(this Enum variable, Enum value)
{
if (variable == null)
return false;
if (value == null)
throw new ArgumentNullException("value");
// Not as good as the .NET 4 version of this function, but should be good enough
if (!Enum.IsDefined(variable.GetType(), value))
{
throw new ArgumentException(string.Format(
"Enumeration type mismatch. The flag is of type '{0}', was expecting '{1}'.",
value.GetType(), variable.GetType()));
}
ulong num = Convert.ToUInt64(value);
return ((Convert.ToUInt64(variable) & num) == num);
}
}
注意:
如果您将负数定义为标记枚举常量,请谨慎使用,因为许多标记位置可能设置为1,这可能会使您的代码混淆并鼓励编码错误。
答案 1 :(得分:6)
不确定您是否使用.NET 4.0,但它附带静态方法Enum.HasFlags()
。
- 删除了代码(已接受的解决方案已经删除) -
答案 2 :(得分:5)
这是我的方法,这是类型安全,不做任何装箱或拆箱。如果类型不是枚举,则抛出异常。
如果你想将它变成一个将被输入Enum的公共静态方法,你可以使用一种技术,但它不能是一种扩展方法。
也没有必要检查null,因为struct contraint也阻止了可以为空的枚举。我不认为要改进这些代码还有很多工作要做,除了可以用F#或C ++ / CLI编写代码,以便你可以对它进行枚举约束。
我的想法是使用表达式树构建一个函数,它将枚举转换为long,如果它只是一个基于ulong的enum,或ulong然后和它们,基本上生成::
return value & flag == flag
public static class EnumExtensions
{
#region Public Static Methods
/// <summary>
/// Determines whether the specified value has flags. Note this method is up to 60 times faster
/// than the one that comes with .NET 4 as it avoids any explict boxing or unboxing.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="flag">The flag.</param>
/// <returns>
/// <c>true</c> if the specified value has flags; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentException">If TEnum is not an enum.</exception>
public static bool HasFlags<TEnum>(this TEnum value, TEnum flag) where TEnum:struct,IComparable,IConvertible,IFormattable
{
return EnumExtensionsInternal<TEnum>.HasFlagsDelegate(value, flag);
}
#endregion Public Static Methods
#region Nested Classes
static class EnumExtensionsInternal<TEnum> where TEnum : struct,IComparable, IConvertible, IFormattable
{
#region Public Static Variables
/// <summary>
/// The delegate which determines if a flag is set.
/// </summary>
public static readonly Func<TEnum, TEnum, bool> HasFlagsDelegate = CreateHasFlagDelegate();
#endregion Public Static Variables
#region Private Static Methods
/// <summary>
/// Creates the has flag delegate.
/// </summary>
/// <returns></returns>
private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate()
{
if(!typeof(TEnum).IsEnum)
{
throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
}
ParameterExpression valueExpression = Expression.Parameter(typeof(TEnum));
ParameterExpression flagExpression = Expression.Parameter(typeof(TEnum));
ParameterExpression flagValueVariable = Expression.Variable(Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long));
Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
Expression.Block(
new[] { flagValueVariable },
Expression.Assign(
flagValueVariable,
Expression.Convert(
flagExpression,
flagValueVariable.Type
)
),
Expression.Equal(
Expression.And(
Expression.Convert(
valueExpression,
flagValueVariable.Type
),
flagValueVariable
),
flagValueVariable
)
),
valueExpression,
flagExpression
);
return lambdaExpression.Compile();
}
#endregion Private Static Methods
}
#endregion Nested Classes
}
由于我忘记了上面的表达式树是.NET 4,因此只有以下方法才能在.NET 3.5中工作以创建相同的表达式树::
private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate2()
{
if(!typeof(TEnum).IsEnum)
{
throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
}
ParameterExpression valueExpression = Expression.Parameter(
typeof(TEnum),
typeof(TEnum).Name
);
ParameterExpression flagExpression = Expression.Parameter(
typeof(TEnum),
typeof(TEnum).Name
);
var targetType = Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long);
Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
Expression.Equal(
Expression.And(
Expression.Convert(
valueExpression,
targetType
),
Expression.Convert(
flagExpression,
targetType
)
),
Expression.Convert(
flagExpression,
targetType
)
),
valueExpression,
flagExpression
);
return lambdaExpression.Compile();
}
这个版本应该在.NET 3.5中编译,如果没有,我就无法理解为什么。
答案 3 :(得分:2)
不幸的是,没有一种方法可以像这样制作扩展方法。为了实现这一点,您需要一个以enum
值运行的通用方法。不幸的是,没有办法将泛型参数限制为枚举
// Ilegal
public static bool Contains<T>(this T value, T flag) where T : enum {
...
}
我提出的最好的是以下
public static bool HasFlag<T>(this System.Enum e, T flag)
{
var intValue = (int)(object)e;
var intFlag = (int)(object)flag;
return (intValue & intFlag) != 0;
}
然而,它在几个方面受到限制
enum
值均为int
。 e
为null
答案 4 :(得分:2)
您基本上可以使用现有的扩展方法,使用Enum
类型而不是MyEnum
。那么问题是它不知道枚举是标志而且不允许&
运算符,所以你只需要将枚举值转换为数字。
public static bool Contains(this Enum keys, Enum flag)
{
if (keys.GetType() != flag.GetType())
throw new ArgumentException("Type Mismatch");
return (Convert.ToUInt64(keys) & Convert.ToUInt64(flag)) != 0;
}
进行良好衡量的单元测试:
[TestMethod]
public void TestContains()
{
var e1 = MyEnum.One | MyEnum.Two;
Assert.IsTrue( e1.Contains(MyEnum.Two) );
var e2 = MyEnum.One | MyEnum.Four;
Assert.IsFalse(e2.Contains(MyEnum.Two));
}
答案 5 :(得分:2)
为.NET Framework 3.5实现 HasFlag 功能的另一种方法。
public static bool HasFlag(this Enum e, Enum flag)
{
// Check whether the flag was given
if (flag == null)
{
throw new ArgumentNullException("flag");
}
// Compare the types of both enumerations
if (e.GetType() != (flag.GetType()))
{
throw new ArgumentException(string.Format(
"The type of the given flag is not of type {0}", e.GetType()),
"flag");
}
// Get the type code of the enumeration
var typeCode = e.GetTypeCode();
// If the underlying type of the flag is signed
if (typeCode == TypeCode.SByte || typeCode == TypeCode.Int16 || typeCode == TypeCode.Int32 ||
typeCode == TypeCode.Int64)
{
return (Convert.ToInt64(e) & Convert.ToInt64(flag)) != 0;
}
// If the underlying type of the flag is unsigned
if (typeCode == TypeCode.Byte || typeCode == TypeCode.UInt16 || typeCode == TypeCode.UInt32 ||
typeCode == TypeCode.UInt64)
{
return (Convert.ToUInt64(e) & Convert.ToUInt64(flag)) != 0;
}
// Unsupported flag type
throw new Exception(string.Format("The comparison of the type {0} is not implemented.", e.GetType().Name));
}
此扩展方法支持枚举的所有可能类型(byte
,sbyte
,short
,ushort
,int
,uint
,long
和ulong
)。基本上,该方法检查给定的枚举是否有符号/无符号,并将该标志转换为枚举所支持类型的最大大小的类型。然后,使用&
运算符执行简单比较。
正如其他帖子中所解释的那样,我们无法使用枚举来定义泛型类型的约束,并且使用带有struct
约束的泛型是没有意义的,因为w开发人员可以插入其他枚举类型或结构然后。所以,我认为最好不要使用泛型方法。
答案 6 :(得分:1)
我在这里有另一种方法,我只是使用Delegate.CreateDelegate允许在Enum的方法和它们的底层类型之间进行转换这一事实。以下方法很像我以前的答案,但我觉得对于不懂表达式树语法的人来说可能更容易阅读。基本上我们知道Enums只有8种可能的底层类型,因此我们只为它可以使用的每个调用创建一个静态方法。由于我要简洁,我使用匿名方法,这些方法恰好与可能的类型代码值命名相同。这种方法适用于.Net 3.5 ::
public static class EnumHelper
{
delegate bool HasFlag<T>(T left,T right);
static readonly HasFlag<Byte> Byte = (x,y)=> (x&y) ==y;
static readonly HasFlag<SByte> Sbyte = (x,y)=> (x&y) ==y;
static readonly HasFlag<Int16> Int16 = (x,y)=> (x&y) ==y;
static readonly HasFlag<UInt16> UInt16 = (x,y)=> (x&y) ==y;
static readonly HasFlag<Int32> Int32 = (x,y)=> (x&y) ==y;
static readonly HasFlag<UInt32> UInt32 = (x,y)=> (x&y) ==y;
static readonly HasFlag<Int64> Int64 = (x,y)=> (x&y) ==y;
static readonly HasFlag<UInt64> UInt64 = (x,y)=> (x&y) ==y;
public static bool HasFlags<TEnum>(this TEnum @enum,TEnum flag) where TEnum:struct,IConvertible,IComparable,IFormattable
{
return Enum<TEnum>.HasFlag(@enum,flag);
}
class Enum<TEnum> where TEnum:struct,IConvertible,IComparable,IFormattable
{
public static HasFlag<TEnum> HasFlag = CreateDelegate();
static HasFlag<TEnum> CreateDelegate()
{
if (!typeof(TEnum).IsEnum) throw new ArgumentException(string.Format("{0} is not an enum", typeof(TEnum)), typeof(Enum<>).GetGenericArguments()[0].Name);
var delegateName = Type.GetTypeCode(typeof(TEnum)).ToString();
var @delegate = typeof(EnumHelper).GetField(delegateName,BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as Delegate;
return Delegate.CreateDelegate(typeof(HasFlag<TEnum>), @delegate.Method) as HasFlag<TEnum>;
}
}
}
答案 7 :(得分:0)
这是一个应该有效的例子。
public static bool IsValid<T>(this T value)
{
return Enum.IsDefined(value.GetType(), value);
}