【问题标题】:Generic single flag enumeration for bitmask enums位掩码枚举的通用单标志枚举
【发布时间】:2019-12-09 11:04:10
【问题描述】:

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

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 并检测单个标志,因为它们是 2 的幂,在此处快速搜索表明这一点

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));
}

有什么办法吗?

【问题讨论】:

    标签: c# enums ienumerable bitmask


    【解决方案1】:

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

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

    按照 cmets 中的建议,以下是优化:

    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;
    

    【讨论】:

    • 这确实应该有效,并且它可能是针对泛型限制的可用解决方法中最丑的,但是:它真的非常低效 - 大量的装箱和类型测试; IMO,这将极大地受益于静态提升 - 可能类似于 gist.github.com/mgravell/4e684204899b4ef3a33bb1c495b4fbe2
    • 谢谢@MarcGravell,我同意。如果您愿意,请随时将其编辑到此答案中:)。
    • 添加了优化建议和接受的答案,谢谢
    【解决方案2】:

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

    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 倍,但对于如此快速的操作,这可能并不重要。它仍然会进行投射。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-14
      • 1970-01-01
      相关资源
      最近更新 更多