generics扩展方法来查看一个枚举是否包含一个标志
考虑到这一点:
[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; } }
是否可以编写一个通用版本的Contains,可以用于任何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
参数可能实际上是几个标志,常识的事情要做的只有当keys
包含所有他们。 此外,我不会检查空值,甚至确保它们是相同的types。 我可能想要使用不同的types。
我基于一堆SO和Googlesearch,并使用reflection器来查看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); } }
笔记:
- 这处理空值
- 是否检查types
- 转换为ulong,并可以处理任何正的枚举值。 微软提醒不要使用负面的枚举枚举:
如果将负数定义为枚举常量,请谨慎使用,因为许多标记位置可能设置为1,这可能会使代码混淆并鼓励编码错误。
不知道你是否使用.NET 4.0,但它带有静态方法Enum.HasFlags()
。
– 删除代码(已接受的解决scheme已经) –
这是我的方法,这是types安全,不做任何拳击或拆箱。 如果该types不是枚举,则会引发exception。 如果你想把它变成一个公共的静态方法,你可以使用这种方法,这个方法将被input到Enum的方法中,但是它不能作为扩展方法。 也不需要检查null,因为struct contraint也阻塞了可为null的枚举。 我不认为要改进这段代码还有很多事情要做,除了写在F#或者C ++ / CLI中,这样你就可以对它施加一个枚举约束。 这个想法是使用expression式树来build立一个函数,该函数将枚举转换为long,如果它不是一个基于ulong的枚举,或者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 }
因为我忘了上面的expression式是.NET 4,只有下面的方法可以在.NET 3.5中工作来创build相同的expression式树::
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中编译,如果没有,我不明白为什么。
基本上可以使用现有的扩展方法,而不是使用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; }
而一个好的措施的unit testing:
[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)); }
不幸的是,没有一个好方法来做这样的扩展方法。 为了这个工作,你需要有一个enum
值操作的generics方法。 不幸的是,没有办法将generics参数限制为一个枚举
// 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; }
然而这在几个方面是有限的
- 由于没有要求,值和标志具有相同的types,因此不安全
- 假设所有
enum
值都是基于int
的。 - 导致拳击发生一个简单的位检查
- 如果
e
为null
会抛出
这是应该工作的一个例子。
public static bool IsValid<T>(this T value) { return Enum.IsDefined(value.GetType(), value); }
我在这里有另外一个方法,我只是用Delegate.CreateDelegate允许在Enum和它们的基础types的方法之间进行转换的事实来快速编译出来的。 下面的方法和我之前的回答非常相似,但是对于那些不知道expression式树语法的人来说,我觉得可能更容易阅读。 基本上我们知道,枚举只有8个可能的基础types,所以我们只是为每个可以使用的调用创build一个静态方法。 由于我要简洁,我使用的匿名方法碰巧被命名为可能的typecode值相同的东西。这种方法将在.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>; } } }
为.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)); }
这个扩展方法支持所有可能的枚举types( byte
, sbyte
, short
, ushort
, int
, uint
, long
和ulong
)。 基本上,该方法检查给定枚举是否有符号/无符号,并将该标志转换为枚举支持types的最大大小的types。 然后,使用&
运算符执行一个简单的比较。
正如其他文章中所解释的,我们不能用枚举来定义genericstypes的约束,并且使用带有struct
约束的generics是没有意义的,因为开发人员可以插入其他枚举types或结构。 所以,我认为最好不要使用generics方法。