Enum.HasFlag,为什么没有Enum.SetFlag?
我必须为我声明的每个标志类型构建一个扩展方法,如下所示:
public static EventMessageScope SetFlag(this EventMessageScope flags, EventMessageScope flag, bool value) { if (value) flags |= flag; else flags &= ~flag; return flags; }
为什么没有一个Enum.SetFlag
就像有一个Enum.HasFlag
?
另外,为什么这总不起作用?
public static bool Get(this EventMessageScope flags, EventMessageScope flag) { return ((flags & flag) != 0); }
例如,如果我有:
var flag = EventMessageScope.Private;
并检查它:
if(flag.Get(EventMessageScope.Public))
其中EventMessageScope.Public
确实是EventMessageScope.Private | EventMessageScope.PublicOnly
EventMessageScope.Private | EventMessageScope.PublicOnly
,它返回true。
如果不是,因为Private
不公开,只是公开一半。
同样适用于:
if(flag.Get(EventMessageScope.None))
哪个返回false
,除了范围实际上是None
( 0x0
),它应该总是返回true?
&
运营商将使用a & b
与b & a
相同的答案,因此
(EventMessaageScope.Private)。获取(EventMessageScope.Private | EventMessageScope.PublicOnly)
和写作一样
(EventMessageScope.Private | EventMessageScope.PublicOnly)。获取(EventMessaageScope.Private)
如果您只想知道该值是否与 EventMessaageScope.Public 相同 ,那么只需使用equals :
EventMessageScope.Private == EventMessageScope.Public
对于(EventMessageScope.None).Get(EventMessaageScope.None)
您的方法将始终返回false
,因为None == 0
并且仅当AND操作的结果不为零时才返回true。 0 & 0 == 0
。
为什么没有一个Enum.SetFlag就像有一个Enum.HasFlag?
HasFlag
作为按位操作需要更复杂的逻辑并重复相同的标志两次
myFlagsVariable= ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );
所以MS决定实施它。
SetFlag和ClearFlag在C#中简洁明了
flags |= flag;// SetFlag flags &= ~flag; // ClearFlag
但不幸的是不直观。 每当我需要设置(或清除)一个标志时,我会花几秒钟(或几分钟)来思考:该方法的名称是什么? 为什么它没有在intellisense中显示? 或者不,我必须使用按位运算。 注意,有些开发人员也会问:什么是按位操作?
是否应创建SetFlag和ClearFlag扩展名 – YES出现在intellisense中。
是否应该由开发人员使用SetFlag和ClearFlag扩展 – 否,因为它们效率不高。
我们在库的类EnumFlagsHelper中创建了扩展,就像在SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier中一样 ,但是将函数命名为SetFlag而不是Include和ClearFlag而不是Remove。
在SetFlag方法的主体(以及摘要评论)中我决定添加
Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n flags |= flag;// SetFlag")
并且应该向ClearFlag添加类似的消息
Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n flags &= ~flag; // ClearFlag ")
public static class SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier { public static T IncludeAll(this Enum value) { Type type = value.GetType(); object result = value; string[] names = Enum.GetNames(type); foreach (var name in names) { ((Enum) result).Include(Enum.Parse(type, name)); } return (T) result; //Enum.Parse(type, result.ToString()); } /// /// Includes an enumerated type and returns the new value /// public static T Include (this Enum value, T append) { Type type = value.GetType(); //determine the values object result = value; var parsed = new _Value(append, type); if (parsed.Signed is long) { result = Convert.ToInt64(value) | (long) parsed.Signed; } else if (parsed.Unsigned is ulong) { result = Convert.ToUInt64(value) | (ulong) parsed.Unsigned; } //return the final value return (T) Enum.Parse(type, result.ToString()); } /// /// Check to see if a flags enumeration has a specific flag set. /// /// Flags enumeration to check /// Flag to check for /// 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); } /// /// Removes an enumerated type and returns the new value /// public static T Remove (this Enum value, T remove) { Type type = value.GetType(); //determine the values object result = value; var parsed = new _Value(remove, type); if (parsed.Signed is long) { result = Convert.ToInt64(value) & ~(long) parsed.Signed; } else if (parsed.Unsigned is ulong) { result = Convert.ToUInt64(value) & ~(ulong) parsed.Unsigned; } //return the final value return (T) Enum.Parse(type, result.ToString()); } //class to simplfy narrowing values between //a ulong and long since either value should //cover any lesser value private class _Value { //cached comparisons for tye to use private static readonly Type _UInt32 = typeof (long); private static readonly Type _UInt64 = typeof (ulong); public readonly long? Signed; public readonly ulong? Unsigned; public _Value(object value, Type type) { //make sure it is even an enum to work with if (!type.IsEnum) { throw new ArgumentException( "Value provided is not an enumerated type!"); } //then check for the enumerated value Type compare = Enum.GetUnderlyingType(type); //if this is an unsigned long then the only //value that can hold it would be a ulong if (compare.Equals(_UInt32) || compare.Equals(_UInt64)) { Unsigned = Convert.ToUInt64(value); } //otherwise, a long should cover anything else else { Signed = Convert.ToInt64(value); } } } }
我做了一些适合我的事情,这很简单……
public static T SetFlag(this Enum value, T flag, bool set) { Type underlyingType = Enum.GetUnderlyingType(value.GetType()); // note: AsInt mean: math integer vs enum (not the c# int type) dynamic valueAsInt = Convert.ChangeType(value, underlyingType); dynamic flagAsInt = Convert.ChangeType(flag, underlyingType); if (set) { valueAsInt |= flagAsInt; } else { valueAsInt &= ~flagAsInt; } return (T)valueAsInt; }
用法:
var fa = FileAttributes.Normal; fa = fa.SetFlag(FileAttributes.Hidden, true);
回答你的部分问题:Get函数根据二进制逻辑正常工作 – 它检查任何匹配。 如果要匹配整个标志集,请考虑以下内容:
return ((flags & flag) != flag);
关于“为什么不存在SetFlag”……可能是因为它并不是真的需要。 标志是整数。 已有一个处理这些的约定,它也适用于标志。 如果你不想用|
来写它 和&
– 这就是自定义静态插件的用途 – 您可以在演示自己时使用自己的function:)
很久以前,Enums被C语言搞砸了。 在C#语言中有一些类型安全对于设计者来说很重要,当底层类型可以是字节和长整数之间的任何东西时,没有空间容纳Enum.SetFlags。 另一个C引发的问题顺便说一句。
处理它的正确方法是明确地内联编写这种代码,而不是试图将其推入扩展方法。 您不想在C#语言中编写C宏。
对于任何枚举,这是SetFlag的另一种快速而又脏的方法:
public static T SetFlag(this T flags, T flag, bool value) where T : struct, IComparable, IFormattable, IConvertible { int flagsInt = flags.ToInt32(NumberFormatInfo.CurrentInfo); int flagInt = flag.ToInt32(NumberFormatInfo.CurrentInfo); if (value) { flagsInt |= flagInt; } else { flagsInt &= ~flagInt; } return (T)(Object)flagsInt; }
我找到的原因是因为枚举是一个值类型,所以你不能传入它并设置它的类型。 对于那些认为它很愚蠢的人,我这样对你说:并非所有开发人员都理解位标志以及如何打开或关闭它们(这不太直观)。
不是一个愚蠢的想法,只是不可能。