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 ,除了范围实际上是None0x0 ),它应该总是返回true?

&运营商将使用a & bb & 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; } 

我找到的原因是因为枚举是一个值类型,所以你不能传入它并设置它的类型。 对于那些认为它很愚蠢的人,我这样对你说:并非所有开发人员都理解位标志以及如何打开或关闭它们(这不太直观)。

不是一个愚蠢的想法,只是不可能。