如何避免不可能的枚举值?

时间:2014-09-09 20:19:47

标签: c# enums bit-manipulation bitwise-operators

说我有以下枚举:

[Flags]
enum IntFlags
{
  A = 1 << 0,
  B = 1 << 1,
  C = 1 << 2,

  AAndB = A | B
}

我想构建一个帮助方法来设置如下的标志:

private static IntFlags Set(IntFlags values, IntFlags target)
{
  return target | values;
}

有没有灵活的方法来进行一些健全性检查?就像不应该设置枚举中不存在的值或者不是现有值的组合。

以下内容不应该是可能的:

IntFlags flags = 0;
flags = Set(0, flags);
flags = Set((IntFlags)int.MaxValue, flags);
flags = Set(~IntFlags.A, flags);

我想只是对现有的值进行检查,如:

private static IntFlags Set(IntFlags values, IntFlags target)
{
  if (!Enum.GetValues(typeof(IntFlags)).Cast<IntFlags>().Contains(values))
    throw new ArgumentException();

  return target | values;
}

因为设置

等值而无法工作
IntFlags flags = 0;
flags = Set(IntFlags.A | IntFlags.C, flags);
应该允许

我认为这可行:

IntFlags Set(IntFlags values, IntFlags target)
{      
  int intValue;
  if (int.TryParse(values.ToString(), out intValue))
  {
    throw new ArgumentException();
  }

  ...
 }

但这取决于我在枚举上使用Flags属性这一事实。

修改

我现在找到了Enum.IsDefined()方法。它适用于

Assert.False(Enum.IsDefined(typeof(IntFlags), (IntFlags)int.MaxValue));
Assert.False(Enum.IsDefined(typeof(IntFlags), 0));
Assert.False(Enum.IsDefined(typeof(IntFlags), ~IntFlags.A));

但是我的要求没有合并值

Assert.True(Enum.IsDefined(typeof(IntFlags), IntFlags.A | IntFlags.C)); //fails

3 个答案:

答案 0 :(得分:6)

无法静态禁止Enum的用户创建表示基础类型的任何值的枚举。您只能在运行时进行此类验证,如问题中所示。

如果你只是在寻找一种简单的验证方法,在运行时,如果一个特定的枚举值是一个有效的标志组合,那么这是一个足够简单的检查,假设定义的枚举值实际上增加了2的幂。对于这样的枚举,所有有效值将从0到1小于下一个最大2的幂,因此检查将变为:

if((int)values > 0 && (int)values >= 1 << 3))
    throw new ArgumentException();

从技术上讲,您可以通过映射每个值来删除所有可能的未映射值,但这不是特别实用解决方案。

如果其中任何一个不是一个选项,那么你剩下的就是不使用Enum,而是使用你自己的自定义类型,它具有你想要的确切语义:

public class IntFlags
{
    public int Value { get; private set; }
    private IntFlags(int value)
    {
        this.Value = value;
    }

    public static readonly IntFlags A = new IntFlags(1);
    public static readonly IntFlags B = new IntFlags(2);
    public static readonly IntFlags C = new IntFlags(4);

    public static IntFlags operator |(IntFlags first, IntFlags second)
    {
        return new IntFlags(first.Value | second.Value);
    }
    public override bool Equals(object obj)
    {
        return Equals(obj as IntFlags);
    }
    public override bool Equals(IntFlags obj)
    {
        return obj != null && Value == obj.Value;
    }
}

答案 1 :(得分:1)

有一个静态的IntFlag字段,你实际上添加这些值是一个很好的解决方案,当你意识到我们有一个Enum.IsDefined,但只是FYI,你也可以采用比特屏蔽的方式。我在LINQPad中把一些东西放在一起给你一个想法:

BitVector32位于System.Collections.Specialized

[Flags]
enum IntFlags
{
    None = 0,
    A = 1 << 0,
    B = 1 << 1,
    C = 1 << 2,

    AAndB = A | B
}

void Main()
{
    BitVector32 bv = new BitVector32(0);
    int mask1 = BitVector32.CreateMask();
    int mask2 = BitVector32.CreateMask(1 << 0);
    int mask3 = BitVector32.CreateMask(1 << 1);
    int mask4 = BitVector32.CreateMask(1 << 2);

    bv[mask1 + mask2 + mask3 + mask4] = true;

    //IntFlags flag1 = IntFlags.A | IntFlags.C;
    IntFlags flag1 = (IntFlags)int.MaxValue;
    if((bv.Data & (int)flag1) == (int)flag1)
        "Yes".Dump();
    else
        "No".Dump();
}

答案 2 :(得分:1)

假设您的[Flags]枚举具有单个位的单个值,那么您应该这样做:

public static Foo SetBits( Foo foo , Foo bar )
{
  Foo value = foo | bar ;

  // throw if any bits are set that are not set in the enum itself.      
  bool isValid = value == (AllBits&value) ;
  if ( !isValid ) throw new InvalidOperationException() ;

  return value ;
}

// Bitwise OR of all bits of all defined values for the enum
private static readonly Foo AllBits = (Foo) Enum
                                      .GetValues(typeof(Foo))
                                      .Cast<int>()
                                      .Aggregate( (x,y) => x|y )
                                      ;