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

use*_*754 4 c# enums bit-manipulation bitwise-operators

说我有以下枚举:

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

  AAndB = A | B
}
Run Code Online (Sandbox Code Playgroud)

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

private static IntFlags Set(IntFlags values, IntFlags target)
{
  return target | values;
}
Run Code Online (Sandbox Code Playgroud)

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

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

IntFlags flags = 0;
flags = Set(0, flags);
flags = Set((IntFlags)int.MaxValue, flags);
flags = Set(~IntFlags.A, flags);
Run Code Online (Sandbox Code Playgroud)

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

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

  return target | values;
}
Run Code Online (Sandbox Code Playgroud)

将无法正常工作,因为设置值如

IntFlags flags = 0;
flags = Set(IntFlags.A | IntFlags.C, flags);
Run Code Online (Sandbox Code Playgroud)

应该被允许.

我认为这可能有效:

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

  ...
 }
Run Code Online (Sandbox Code Playgroud)

但这取决于我在枚举上使用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));
Run Code Online (Sandbox Code Playgroud)

但是我的要求没有合并的价值

Assert.True(Enum.IsDefined(typeof(IntFlags), IntFlags.A | IntFlags.C)); //fails
Run Code Online (Sandbox Code Playgroud)

Ser*_*rvy 6

没有办法静态地禁止用户Enum创建表示底层类型的任何值的枚举.您只能在运行时进行此类验证,如问题中所示.

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

if((int)values > 0 && (int)values >= 1 << 3))
    throw new ArgumentException();
Run Code Online (Sandbox Code Playgroud)

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

如果其中任何一个都不是一个选项,你剩下的就是不使用an 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;
    }
}
Run Code Online (Sandbox Code Playgroud)