C# 枚举类型安全性

5
有没有办法强制 C# 枚举只接受几个明确命名的常量之一,或者有没有其他功能可以实现这一点?C# 参考文献中有这个事后的解释:可以向枚举类型分配任意整数值。但是,不应该这样做,因为隐含的期望是一个枚举变量将只保存由枚举定义的值之一。将任意值分配给枚举类型的变量将引入高风险的错误。(有一种新语言被设计用于允许这种松散性。这让我感到困惑。)
2个回答

4
据我所知,你无法阻止C#允许在枚举类型和整数之间进行转换。
作为一种解决方法,您可以使用具有受限实例化的自定义类型。其使用方式类似,但您还可以为此类型定义方法和运算符。
假设您有以下枚举:
enum DayOfWeek
{
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday 
}

您可以使用封闭类来替代它。其优点是您可以免费获得比较(因为对于此情况,值比较和引用比较是等效的)。缺点是(就像C#中的所有引用类型一样),它是可空的。
sealed class DayOfWeek
{
    public static readonly DayOfWeek Monday = new DayOfWeek(0);
    public static readonly DayOfWeek Tuesday = new DayOfWeek(1);
    public static readonly DayOfWeek Wednesday = new DayOfWeek(2);
    public static readonly DayOfWeek Thursday = new DayOfWeek(3);
    public static readonly DayOfWeek Friday = new DayOfWeek(4);
    public static readonly DayOfWeek Saturday = new DayOfWeek(5);
    public static readonly DayOfWeek Sunday = new DayOfWeek(6);

    private readonly int _value;

    private DayOfWeek(int value) 
    {
        _value = value;
    }
}

或者你可以使用结构体。优点是它不可为空,因此更类似于枚举。缺点是你必须手动实现比较代码:

struct DayOfWeek
{
    public static readonly DayOfWeek Monday = new DayOfWeek(0);
    public static readonly DayOfWeek Tuesday = new DayOfWeek(1);
    public static readonly DayOfWeek Wednesday = new DayOfWeek(2);
    public static readonly DayOfWeek Thursday = new DayOfWeek(3);
    public static readonly DayOfWeek Friday = new DayOfWeek(4);
    public static readonly DayOfWeek Saturday = new DayOfWeek(5);
    public static readonly DayOfWeek Sunday = new DayOfWeek(6);

    private readonly int _value;

    private DayOfWeek(int value)
    {
        _value = value;
    }

    public bool Equals(DayOfWeek other)
    {
        return _value == other._value;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        return obj is DayOfWeek && Equals((DayOfWeek)obj);
    }

    public override int GetHashCode()
    {
        return _value;
    }

    public static bool operator ==(DayOfWeek op1, DayOfWeek op2)
    {
        return op1.Equals(op2);
    }

    public static bool operator !=(DayOfWeek op1, DayOfWeek op2)
    {
        return !(op1 == op2);
    }
}

1
有一件事情会让情况变得更糟糕 - 你现在可以将 null 赋值给 DayOfWeek,因为它是一个类。 - MarcinJuraszek
@MarcinJuraszek 没错。我已经包含了另一种方法。 - Theodoros Chatzigiannakis
1
“struct”方法会带来另一个问题——您可以通过调用无参数构造函数创建一个额外的值。编译器默认为每个结构定义它。 - MarcinJuraszek
@MarcinJuraszek,枚举也是一样的 - 即使零值对于特定的枚举无效,您始终可以获得所有位都设置为零的“默认”值。从这个意义上讲,结构体方法并不比常规枚举更少类型安全。除非我漏掉了什么。 - Theodoros Chatzigiannakis

2

将任何整数转换为枚举类型主要是出于性能原因,但是作为值类型实现的枚举无法防止包含未定义的值。考虑以下枚举:

public enum Condition {
  Right = 1,
  Wrong = 2
}

即使将枚举变量的赋值限制为仅限于已定义的值,您仍然可以通过将其放入类中来创建未定义的值:
public class Demo {
  public Condition Cond;
}

当你创建一个类的实例时,成员变量会被初始化为零,因此Cond成员变量将具有(Condition)0未定义的值。
你可以创建一个包装类来确保枚举值属于定义的值之一。
public sealed class SafeEnum<T> where T : struct {

  public T Value { get; private set; }

  public SafeEnum(T value) {
    if (!(value is Enum)) {
      throw new ArgumentException("The type is not an enumeration.");
    }
    if (!Enum.IsDefined(typeof(T), value)) {
      throw new ArgumentException("The value is not defined in the enumeration.");
    }
    Value = value;
  }

}

例子:

var cond = new SafeEnum<Condition>(Condition.Right); // works
Condition d = cond.Value;

var cond = new SafeEnum<int>(42); // not an enum

var cond = new SafeEnum<Condition>((Condition)42); // not defined

该类的实例只能包含在枚举中定义的值,否则构造函数将不允许创建该实例。

由于该类是不可变的,因此其值不能更改为未定义的值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接