【问题标题】:Neatest way to 'OR' all values in a Flagged Enum?在标记的枚举中“或”所有值的最佳方法?
【发布时间】:2013-03-08 16:06:23
【问题描述】:

鉴于enum

[Flags]
public enum mytest
{
    a = 1,
    b = 2,
    c = 4
}

我想出了两种方法来表示单个变量中的所有值:

    var OR1 = (mytest)Enum.GetNames(typeof(mytest)).Sum(a => (int)Enum.Parse(typeof(mytest), a));
    var OR2 = (mytest)(typeof(mytest).GetEnumValues() as mytest[]).Sum(a => (int)a);

现在,虽然它们都可以工作,但有没有更简洁的方法?可能是我缺少的 .NET 方法?

编辑:为澄清起见,我需要动态函数 - 我不想通过指定每个 enum 值来计算它。

【问题讨论】:

  • 你也可以有一个 All 枚举成员,它是 sum。
  • 半严肃的建议:在某些情况下你可以这样做:int all = ~0;

标签: c# enums


【解决方案1】:

如果拥有All 成员有意义,请直接提供:

[Flags]
public enum mytest
{
    a = 1,
    b = 2,
    c = 4,
    All = 7
}

不过,更惯用的写法可能是:

[Flags]
public enum MyTest
{
    A = 1,
    B = 1 << 0x01,
    C = 1 << 0x02,
    All = A | B | C
}

这显示了枚举值的逻辑顺序,并且在 All 的情况下,可以很容易地添加另一个成员。

【讨论】:

  • 不是我的反对意见,但也许硬编码特定枚举以代表所有其他枚举的想法并不理想 - 如果我添加一个枚举值,那么我每次都必须进行两次更改。加上其他示例中的代码不起作用。
  • 在创建标志枚举时提供一个等于 0 的 None 成员也是很常见的。
  • 如果值重叠,Sum 将不起作用。例如{ 1, 2, 4, 7, 8 }。 Or 是 15,Sum 是 22
  • 另外,我通常使用十六进制指定标志值,因为它比十进制更容易看到进度。
  • 另外,我会写All = a | b | c; 而不是All = 7;。然后添加一个新的枚举成员就像将| newEnumMember 附加到All 的初始化一样简单。
【解决方案2】:

使用Enumerable.Aggregate() 按位或将它们组合在一起。即使您有代表多个设置位的枚举值,这也将起作用,而不是Sum()

var myTestValues = (MyTest[]) typeof(MyTest).GetEnumValues();
var sum = myTestValues.Aggregate((a, b) => a | b);
sum.Dump();

使这个泛型有点棘手,因为您不能将泛型类型限制为枚举,原始类型之间也没有任何子类型关系。我能想到的最好的假设是底层类型是int,这在大多数情况下应该足够好:

TEnum AllEnums<TEnum>() 
{
    var values = typeof(TEnum).GetEnumValues().Cast<int>();
    return (TEnum) (object) values.Aggregate((a,b) => a|b);
}

【讨论】:

  • 为什么需要Enum.ToObject?我不是 100% 确定,但演员不是自己工作的吗?
  • @p.s.w.g 它没有,因为你不能将不相关的类型(int 强制转换为不受约束的TEnum)。为了使强制转换合法,编译器必须知道它至少有可能有意义,并且它知道int 在没有某种显式转换的情况下实际上不能转换为任何东西。 (我承认这主要是对规则的猜测,但无论哪种方式编译器都会抱怨直接转换。)
  • @p.s.w.g 从头开始​​,事实证明你是对的。 (即使双重演员结构有点搞笑。)
  • @p.s.w.g 我真的很想知道为什么编译器会接受它而不是直接转换。
【解决方案3】:

对于泛型方法,使用Linq的Enumerable.Aggregate扩展方法;

var flags = Enum.GetValues(typeof(mytest))
                .Cast<int>()
                .Aggregate(0, (s, f) => s | f);

或者在包装方法中

TEnum GetAll<TEnum>() where TEnum : struct
{
    return (TEnum) (object)
            Enum.GetValues(typeof(TEnum))
                .Cast<int>()
                .Aggregate(0, (s, f) => s | f);
}

这个双重施法技巧的全部功劳归于@millimoose

【讨论】:

  • +1 只需将它放在一个通用方法中,您就可以将它用于任何枚举。 T GetAll&lt;T&gt;() { return (T)(object)Enum.GetValues(typeof(T))... }
  • 不幸的是,如果底层枚举类型的大小与 int 不同,则会引发无效的强制转换异常。我在下面有一个解决方案
【解决方案4】:

确保所有枚举位都设置的最简单方法是设置所有位:

mytest allValues = (mytest)int.MaxValue;

这假设设置不对应于任何枚举的位没有问题,但这很可能是真的。您可以将其与任何枚举值进行 AND 运算,结果将成真,这很可能是最终目标。

【讨论】:

  • 您确定此代码有效吗?我想我不久前尝试过类似的东西,但没有。
  • @maxp 在一些简单的测试中对我来说效果很好。你能想出一个不起作用的测试用例吗?
  • 这确实有效。只是测试它,因为我认为演员会引发异常,但不是。它有效。
  • @istepaniuk 显然,如果枚举由不同的数字字段支持,例如短或长,您将需要使用适当类型的 MaxValue 方法,但仅此而已需要改变。
  • 您正在创建一个“无效”枚举,您不能简单地将它与 == 进行比较。此外...它匹配任何组合...您如何使用它来检查是否设置了所有标志?
【解决方案5】:

怎么样

var all = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>().Last() * 2 - 1;

基本上

all = max*2-1

这仅适用于从 1 到最大值的所有值。

1,2,4...32,64...

【讨论】:

    【解决方案6】:

    考虑到潜在的类型转换问题,这并不像乍一看那么容易:

    static public TEnum GetAllFlags<TEnum>() where TEnum : struct, IComparable, IFormattable, IConvertible
        {
            unchecked
            {
                if (!typeof(TEnum).IsEnum)
                    throw new InvalidOperationException("Can't get flags from non Enum");
                object val = null;
                switch (Type.GetTypeCode(Enum.GetUnderlyingType(typeof(TEnum))))
                {
                    case TypeCode.Byte:
                    case TypeCode.SByte:
                        val = Enum.GetValues(typeof(TEnum))
                                    .Cast<Byte>()
                                    .Aggregate(default(Byte), ( s, f) => (byte)(s | f));
                        break;
                    case TypeCode.Int16:
                    case TypeCode.UInt16:
                        val = Enum.GetValues(typeof(TEnum))
                                    .Cast<UInt16>()
                                    .Aggregate(default(UInt16), ( s, f) => (UInt16)(s | f));
                        break;
                    case TypeCode.Int32:
                    case TypeCode.UInt32:
                        val = Enum.GetValues(typeof(TEnum))
                                    .Cast<UInt32>()
                                    .Aggregate(default(UInt32), ( s, f) => (UInt32)(s | f));
                        break;
                    case TypeCode.Int64:
                    case TypeCode.UInt64:
                        val = Enum.GetValues(typeof(TEnum))
                                    .Cast<UInt64>()
                                    .Aggregate(default(UInt64), ( s, f) => (UInt64)(s | f));
                        break;
                    default :
                        throw new InvalidOperationException("unhandled enum underlying type");
    
                }
                return (TEnum)Enum.ToObject(typeof(TEnum), val);
            }
        }
    

    有关此类转化的更多信息,请参阅here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-09
      • 2010-12-09
      • 1970-01-01
      相关资源
      最近更新 更多