枚举.HasFlag与按位与运算符检查

5
如果您有一个用于位标志的枚举(enum),即:
[Flags]
internal enum _flagsEnum : byte
{
    None = 0,           //00000000
    Option1 = 1,        //00000001
    Option2 = 1 << 1,   //00000010
    Option3 = 1 << 2,   //00000100
    Option4 = 1 << 3,   //00001000
    Option5 = 1 << 4,   //00010000
    Option6 = 1 << 5,   //00100000
    Option7 = 1 << 6,   //01000000
    Option8 = 1 << 7,   //10000000
    All = Byte.MaxValue,//11111111
}

_flagsEnum myFlagsEnum = _flagsEnum.None;

做两个小的查询比做一个大的查询快吗?

bool hasFlag = myFlagsEnum.HasFlag(_flagsEnum.Option1);

或者去做...
bool hasFlag = myFlagsEnum & _flagsEnum.Option1 != 0

如果检查多个标志之间存在性能差异,请考虑这一点。

通常我会查看引用源代码,但在这种情况下,Enum.HasFlags只是转到一个外部InternalHasFlags,因此我不知道它在做什么。


2
有一些性能成本。请参见:https://dev59.com/k2s05IYBdhLWcg3wPPaB - stephen.vakil
1
似乎是过早的优化。我不喜欢HasFlag的一件事是它接受任何枚举对象作为参数,而不是相同类型的枚举对象。 - Mr Anderson
1
@Yushatak 那我建议你自己编写一个扩展方法,为你的枚举重载 HasFlag() 方法。 - Mr Anderson
1
& 运算符需要一个单处理器指令(TEST),在现代 CPU 上运行时间为 0.25 到 1 个处理器周期。它永远不会比调用 CLR 慢。如果您关心速度,请勿使用 byte 作为枚举基类型,而应使用默认值(int),因为它是最快的。 - Hans Passant
2
更新:现在性能差异已经“扁平化”了。 - Davide Bellone
显示剩余6条评论
2个回答

12

使用 HasFlag 存在性能成本,因为实现会验证您传递的 enum 值是否与标志的类型相同。

除此之外,这个实现高度优化,以避免将较短的类型(例如 byte)提升为 int

switch (pMTThis->GetNumInstanceFieldBytes()) {
case 1:
    cmp = ((*(UINT8*)pThis & *(UINT8*)pFlags) == *(UINT8*)pFlags);
    break;
case 2:
    cmp = ((*(UINT16*)pThis & *(UINT16*)pFlags) == *(UINT16*)pFlags);
    break;
case 4:
    cmp = ((*(UINT32*)pThis & *(UINT32*)pFlags) == *(UINT32*)pFlags);
    break;
case 8:
    cmp = ((*(UINT64*)pThis & *(UINT64*)pFlags) == *(UINT64*)pFlags);
    break;
default:
    // should not reach here.
    UNREACHABLE_MSG("Incorrect Enum Type size!");
    break;
}

可以在这里找到ReflectionEnum::InternalHasFlag的源代码。

虽然成本相对较高,但除了最极端的情况外,这很可能不会有影响。我建议保留它,除非您的分析器指出此调用是程序中最大的瓶颈。


3
此外,由于HasFlag方法需要一个类作为参数,而枚举是一个类,因此还会进行装箱和拆箱。 - Bruno Zell

2

不安全

这个怎么样?在我的基准测试中,比HasFlag快大约25%,比位运算慢10%-15%,但是通用。

也许有人能够优化它。

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static unsafe Boolean HasFlags<T>(T* first, T* second) where T : unmanaged, Enum
{
    Byte* pf = (Byte*) first;
    Byte* ps = (Byte*) second;

    for (Int32 i = 0; i < sizeof(T); i++)
    {
        if ((pf[i] & ps[i]) != ps[i])
        {
                return false;
        }
    }

    return true;
}
    
/// <remarks>Faster analog of Enum.HasFlag</remarks>
/// <inheritdoc cref="Enum.HasFlag"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Boolean HasFlags<T>(this T first, T second) where T : unmanaged, Enum
{
    return HasFlags(&first, &second);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接