我必须为我声明的每个标志类型构建一个扩展方法,如下所示:
public static EventMessageScope SetFlag(this EventMessageScope flags,
EventMessageScope flag, bool value)
{
if (value)
flags |= flag;
else
flags &= ~flag;
return flags;
}
为什么没有像有Enum.HasFlag
一样的Enum.SetFlag
呢?
还有,为什么这并不总是有效?
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
时,返回true。
如果不是,因为Private
不是公开的,它只是一半公开的。
if(flag.Get(EventMessageScope.None))
它返回false
,除了范围实际上是None
(0x0
),当它应该总是返回true?
为什么没有Enum ?SetFlag,比如有一个枚举。hasflag ?
HasFlag
作为位操作需要更复杂的逻辑,并且重复相同的标志两次
myFlagsVariable= ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );
所以微软决定实现它。
SetFlag和ClearFlag在c#中是简洁的
flags |= flag;// SetFlag
flags &= ~flag; // ClearFlag
,但不幸的是不是直观的。每次我需要设置(或清除)一个标志时,我都要花几秒钟(或几分钟)来思考:方法的名称是什么?为什么它没有在智能感知中表现出来?或者不行,我必须使用位运算。注意,一些开发人员也会问:什么是位操作?
是否应该创建SetFlag和ClearFlag扩展-是出现在智能感知
开发者应该使用SetFlag和ClearFlag扩展吗? -不,因为它们效率不高。
我们已经在库的类EnumFlagsHelper中创建了扩展,比如someenumhelpermethodsthatmakedoingwhatyouwantesier,但将函数命名为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 T SetFlag<T>(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);
public static class SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier
{
public static T IncludeAll<T>(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());
}
/// <summary>
/// Includes an enumerated type and returns the new value
/// </summary>
public static T Include<T>(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());
}
/// <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);
}
/// <summary>
/// Removes an enumerated type and returns the new value
/// </summary>
public static T Remove<T>(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);
}
}
}
}
&
运算符将给您a & b
与b & a
相同的答案,因此
(EventMessaageScope.Private) . get (EventMessageScope。Private | EventMessageScope.PublicOnly)
与
相同(EventMessageScope。Private | eventmessaagescope . publiconly).Get(EventMessaageScope.Private)
如果你只是想知道该值是否与EventMessaageScope相同。Public,然后使用 = :
EventMessageScope。Private == EventMessageScope。公共
您的方法将始终为(EventMessageScope.None).Get(EventMessaageScope.None)
返回false
,因为None == 0
并且只有当and操作的结果为而不是零时才返回true。0 & 0 == 0
.
这里是另一种快速而肮脏的方法来为任何Enum设置SetFlag:
public static T SetFlag<T>(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;
}
现在是2021年,c#有很多很好的功能,这意味着应该有一个更优雅的方式来做到这一点。让我们讨论一下前面答案的主张……
权利要求1:关闭一个标志是低效的,因为它使用两个操作,调用另一个方法只会增加更多的开销。
这应该是假的。如果添加了侵略性内联编译器标志,编译器应该将按位操作提升为直接内联操作。如果您正在编写关键代码,则可能需要对其进行基准测试以确认,因为即使在较小的编译器版本之间,结果也会有所不同。但关键是,您应该能够调用一个方便的方法,而无需支付方法查找成本。
声明2:这太冗长了,因为你必须设置标志,然后分配返回值。
这个也应该是假的。c#提供了"ref",它允许你通过引用(在本例中是枚举)直接操作值类型参数。结合侵略性内联,编译器应该足够聪明,可以完全删除ref指针,生成的IL看起来应该和直接内联两个位操作一样。
事项:当然,这些都是理论。也许其他人可以在这里的评论中出现,并从下面建议的代码中检查IL。我自己没有足够的经验(现在也没有时间)来看看假设的说法是否正确。但我认为这个答案仍然值得发布,因为事实是c# 应该能够做我解释的事情。
如果其他人可以确认这一点,我可以相应地更新答案。
public enum MyCustomEnum : long
{
NO_FLAGS = 0,
SOME_FLAG = 1,
OTHER_FLAG = 1 << 1,
YET_ANOTHER_FLAG = 1 << 2,
ANOTHER STILL = 1 << 3
}
public static class MyCustomEnumExt
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TurnOFF(ref this MyCustomEnum status, MyCustomEnum flag)
=> status &= ~flag;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TurnON(ref this MyCustomEnum status, MyCustomEnum flag)
=> status |= flag;
}
你应该能够像这样使用代码:
//Notice you don't have to return a value from the extension methods to assign manually.
MyCustomEnum mc = MyCustomEnum.SOME_FLAG;
mc.TurnOFF(MyCustomEnum.SOME_FLAG);
mc.TurnON(MyCustomEnum.OTHER_FLAG);
即使编译器不能正确地优化它,它仍然非常方便。至少您可以在非关键代码中使用它,并期望具有出色的可读性。
回答你的部分问题:Get函数根据二进制逻辑正常工作-它检查任何匹配。如果您想匹配整个标志集,请考虑这样做:
return ((flags & flag) != flag);
关于"为什么没有SetFlag"…可能是因为它并不是真的需要。标志是整数。已经有一个处理这些的惯例,它也适用于旗帜。如果你不想用|
和&
来编写它——这就是自定义静态插件的作用——你可以像你演示的那样使用你自己的函数:)
枚举很早以前就被C语言禁用了。在c#语言中有一点类型安全对设计者来说是很重要的,没有给Enum留下空间。当底层类型可以是字节和长类型之间的任何类型时,使用SetFlags。另外一个C引起的问题。
处理它的正确方法是显式地内联编写这类代码,并且而不是尝试将其塞进扩展方法中。你不希望用c#语言编写C宏。到目前为止都是很好的答案,但是如果你正在寻找一个更高效的不分配托管内存的简写,你可以使用这个:
using System;
using System.Runtime.CompilerServices;
public static class EnumFlagExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TEnum AddFlag<TEnum>(this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
{
unsafe
{
switch (sizeof(TEnum))
{
case 1:
{
var r = *(byte*)(&lhs) | *(byte*)(&rhs);
return *(TEnum*)&r;
}
case 2:
{
var r = *(ushort*)(&lhs) | *(ushort*)(&rhs);
return *(TEnum*)&r;
}
case 4:
{
var r = *(uint*)(&lhs) | *(uint*)(&rhs);
return *(TEnum*)&r;
}
case 8:
{
var r = *(ulong*)(&lhs) | *(ulong*)(&rhs);
return *(TEnum*)&r;
}
default:
throw new Exception("Size does not match a known Enum backing type.");
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TEnum RemoveFlag<TEnum>(this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
{
unsafe
{
switch (sizeof(TEnum))
{
case 1:
{
var r = *(byte*)(&lhs) & ~*(byte*)(&rhs);
return *(TEnum*)&r;
}
case 2:
{
var r = *(ushort*)(&lhs) & ~*(ushort*)(&rhs);
return *(TEnum*)&r;
}
case 4:
{
var r = *(uint*)(&lhs) & ~*(uint*)(&rhs);
return *(TEnum*)&r;
}
case 8:
{
var r = *(ulong*)(&lhs) & ~*(ulong*)(&rhs);
return *(TEnum*)&r;
}
default:
throw new Exception("Size does not match a known Enum backing type.");
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetFlag<TEnum>(ref this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
{
unsafe
{
fixed (TEnum* lhs1 = &lhs)
{
switch (sizeof(TEnum))
{
case 1:
{
var r = *(byte*)(lhs1) | *(byte*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
case 2:
{
var r = *(ushort*)(lhs1) | *(ushort*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
case 4:
{
var r = *(uint*)(lhs1) | *(uint*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
case 8:
{
var r = *(ulong*)(lhs1) | *(ulong*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
default:
throw new Exception("Size does not match a known Enum backing type.");
}
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ClearFlag<TEnum>(this ref TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
{
unsafe
{
fixed (TEnum* lhs1 = &lhs)
{
switch (sizeof(TEnum))
{
case 1:
{
var r = *(byte*)(lhs1) & ~*(byte*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
case 2:
{
var r = *(ushort*)(lhs1) & ~*(ushort*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
case 4:
{
var r = *(uint*)(lhs1) & ~*(uint*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
case 8:
{
var r = *(ulong*)(lhs1) & ~*(ulong*)(&rhs);
*lhs1 = *(TEnum*)&r;
return;
}
default:
throw new Exception("Size does not match a known Enum backing type.");
}
}
}
}
}
它只需要c# 7.3或更高版本,并且指示编译器接受/不安全代码。
AddFlag,RemoveFlag不修改枚举值你调用它,SetFlag和ClearFlag修改它。这可能是具有最低性能开销的通用解决方案,但它仍然不如直接使用
快。flags |= flag;
flags &= ~flag;
我发现的原因是,因为enum是一种值类型,你不能传递它并设置它的类型。对于所有认为这很愚蠢的人,我要告诉你们:并不是所有的开发人员都理解位标志,以及如何打开或关闭它们(这很不直观)。