位掩码枚举的通用单标志枚举

Generic single flag enumeration for bitmask enums

我正在开发一个包含多个位标志枚举的代码库,看起来像这样

public enum BitMask
{
    None = 0,

    OptionA = 1 << 0,
    OptionB = 1 << 1,

    OptionAandB = OptionA | OptionB,

    All = ~0
} 

我可以使用这个

遍历所有枚举值
public IEnumerable<T> EnumValues<T>()
{
    return Enum.GetValues(typeof(T)).Cast<T>();
}

我正在寻找一种仅迭代单个标志值的通用方法,在本例中为 OptionAOptionB不是 NoneOptionAandBAll。我可以投射到 long 并检测单个标志,因为它们是两个的幂,在这里快速搜索表明这个

public bool IsPowerOfTwo(long val)
{
    return (val != 0) && ((val & (val-1)) == 0) ;
}

非通用版本工作正常

public IEnumerable<BitMask> SingleFlagBitMaskValues()
{
    return Enum.GetValues(typeof(BitMask))
               .Cast<BitMask>()
               .Where(e => IsPowerOfTwo((long)e));
}

但是通用版本无法编译,因为它不喜欢转换为 long

public IEnumerable<TEnum> SingleFlagEnumValues<TEnum>()
{
    return Enum.GetValues(typeof(TEnum))
        .Cast<TEnum>()
        .Where(e => IsPowerOfTwo((long)e));
}

有什么解决办法吗?

您可以使用 Convert.ToInt64,因为它有很多重载:

public IEnumerable<TEnum> SingleFlagEnumValues<TEnum>()
{
    return Enum.GetValues(typeof(TEnum))
        .Cast<TEnum>()
        .Where(e => IsPowerOfTwo(Convert.ToInt64(e)));
}

根据评论建议,这里进行优化:

static class SingleFlagCache<TEnum>
{
    internal static TEnum[] values = Enum.GetValues(typeof(TEnum))
        .Cast<TEnum>()
        .Where(e => IsPowerOfTwo(Convert.ToInt64(e))).ToArray();
}
public static IEnumerable<TEnum> SingleFlagEnumValues<TEnum>()
    => SingleFlagCache<TEnum>.values;

这里有一个可供比较的替代版本:

public static IEnumerable<TEnum> SingleFlagEnumValues<TEnum>() where TEnum: struct, Enum
{
    var type = typeof(TEnum);

    foreach (int value in Enum.GetValues(type))
    {
        if (IsPowerOfTwo(value))
            yield return (TEnum)(object)value;
    }
}

(我的时间表明这比使用 Convert.ToInt64(e) 快 1.5 倍,但对于如此快的操作,这可能并不重要。无论如何它仍然会进行转换。)