【问题标题】:C#: type safety over sets of integers (e. g. enums)C#:整数集的类型安全(例如枚举)
【发布时间】:2025-12-16 20:35:01
【问题描述】:

我有一个案例,我有几组数字(寄存器值)。我想提高可读性并检查适当的类型(只有某些值在某些函数中才有意义)。

在我的特定实现中,我将它们设为枚举 - 所以我现在有一组枚举。

现在我似乎已经结束了这种方法,因为我想将它们划分为某些应用程序的有效枚举集 - 因此函数 A 可以例如将 enumA、enumB 和 enumC 中的值作为输入,但不是 enumD,它是对不同功能的描述。

我已经研究过接口中的枚举和枚举继承——两者都是死胡同,在 C# 中是不可能的。

我现在想知道如何解决这个问题。我想对可能的值进行智能感知,并且还具有一些类型安全性,这样我就不能(好吧,至少在没有恶意强制转换的情况下)输入错误的值。

如何做到这一点?

(可能的解决方案是简单地编写几个采用几个不同枚举的函数 - 仍然可能但不是很好,或者像 Is there a name for this pattern? (C# compile-time type-safety with "params" args of different types) 这样的东西 - 看起来都不太好。)

【问题讨论】:

  • 虽然在 C# 中无法扩展需要访问内部的 Enum,但可以使用扩展方法实现对 num 的许多扩展。你知道如何创建扩展方法吗?
  • 你的意思是我的整数的扩展方法,所以我可以写 2.MyMethod()?如果我理解正确,我不知道这对我有什么帮助。我将如何获得类型安全,甚至是枚举提供的一些排序?
  • 不,你的枚举的扩展方法来扩展它们。您说扩展枚举是一条死胡同,但事实并非如此。由于 Enum 没有内部结构,因此扩展方法在这里就足够了。
  • 如果您确实需要具有内部结构的枚举,那么它们应该是类/对象而不是枚举,如下面的答案之一所示。
  • 你说的是扩展方法?因为我说过 a) 继承和 b) 接口对我来说是一条死胡同。

标签: c# enums type-safety


【解决方案1】:

一种选择是废弃枚举并使用您自己的类来模仿枚举。设置它们需要做更多的工作,但是一旦设置好,它就会很容易使用,并且能够拥有你所描述的功能。

public class Register
{
    private int value;

    internal Register(int value)
    {
        this.value = value;
    }

    public static readonly Register NonSpecialRegister = new Register(0);
    public static readonly Register OtherNonSpecialRegister = new Register(1);

    public static readonly SpecialRegister SpecialRegister 
        = SpecialRegister.SpecialRegister;
    public static readonly SpecialRegister OtherSpecialRegister 
        = SpecialRegister.OtherSpecialRegister;

    public override int GetHashCode()
    {
        return value.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        Register other = obj as Register;
        if (obj == null)
            return false;

        return other.value == value;
    }
}

public class SpecialRegister : Register
{
    internal SpecialRegister(int value) : base(value) { }

    public static readonly SpecialRegister SpecialRegister = new SpecialRegister(2);
    public static readonly SpecialRegister OtherSpecialRegister = new SpecialRegister(3);
}

鉴于此,您可以使用如下方法:

public static void Foo(Register reg)
{
}

这可以使用任何寄存器,并且可以这样调用:

Foo(Register.NonSpecialRegister);
Foo(Register.OtherSpecialRegister);

那么你可以有另一种方法,例如:

public static void Bar(SpecialRegister reg)
{
}

不能接受Register.NonSpecialRegister,但可以接受Register.OtherSpecialRegisterSpecialRegister.SpecialRegister

【讨论】:

  • 看起来它可以工作 - 虽然我需要 1 个额外的继承级别,将 SpecialRegister1 和 SpecialRegister2 集群为允许的 SpecialRegister 集。是的,需要大量的写作。 :)
  • @AndreasReiff 我从代码中假设您可以推断出具有任何深度级别的任意数量的子类型;添加更多内容很乏味且需要大量打字,但在智力上并不难。
  • @AndreasReiff:这就是正确的做法。
【解决方案2】:

听起来您已经用尽了 CLR 上静态类型系统的功能。您仍然可以通过使用一个类包装每个整数来获得运行时验证,该类验证您尝试存储在其中的值实际上是静态集的成员。

如果您有可靠的测试套件或愿意进行手动测试,这至少会发现错误,而不是导致无声数据损坏的错误。

如果您有多个要分开的“集合”,您可以使用类继承或使用一组用户定义的转换运算符来验证转换在运行时是否正常。

我不知道您有什么具体要求,但也许您可以使用基于类的继承来静态检查某些属性。在这种情况下,基类将是更大的集合,而派生类将专门化允许值的集合。

【讨论】:

    【解决方案3】:

    你基本上有两种选择:

    选项 1:多个枚举

    创建多个枚举,每个应用程序一个,并复制每个枚举中的值。然后你可以在它们之间施放。例如:

    enum App1
    {
        Data1 = AppAll.Data1,
        Data2 = AppAll.Data2,
        Data42 = AppAll.Data42,
    }
    
    enum App2
    {
        Data2 = AppAll.Data2,
        Data16 = AppAll.Data16,
        Data42 = AppAll.Data42,
    }
    
    enum AppAll
    {
        Data1 = 1,
        Data2 = 2,
        Data16 = 16,
        Data42 = 42,
    }
    
    App1 value1 = (App1)AppAll.Data2;
    App2 value2 = (App2)value1;
    

    这将为您提供 IntelliSense。

    选项 2:确定哪些是允许的

    创建一个返回允许值的布尔值的方法(这可能是虚拟的,并且对于每个应用程序都被覆盖)。然后在枚举值错误的时候可以抛出异常。

    public bool IsAllowed(AppAll value)
    {
        return value == AppAll.Data2
            || value == AppAll.Data16
            || value == AppAll.Data42;
    }
    
    
    if (!IsAllowed(value))
        throw new ArgumentException("Enum value not allowed.");
    

    这不会为您提供 IntelliSense。


    几点说明:

    • 您不能继承枚举,因为在幕后枚举表示为 structs(即值类型)。
    • 在 C# 中,您可以逐字地将 any 值转换为您的枚举类型,即使它不是枚举类型的成员。例如,即使没有值为1337 的成员,我也可以执行(App1)1337

    【讨论】:

      【解决方案4】:

      如果你想要 compile 类型检查,你最好使用不同的枚举来处理不同的情况。如果您想拥有一个具有所有可能性的主枚举,您可以编写一个测试,以确保您的所有“子”枚举列表都是主枚举的有效子集(就 Int 强制转换而言)。

      作为替代方案,我不得不怀疑(由于没有提供代码,我只能怀疑)是否可能无法更好地为每个枚举选项提供方法的对象。然后,您使用各种方法而不是枚举继承对象。 (毕竟,您似乎在使用 Enums 作为方法签名的代理)。

      【讨论】:

      • 检查有效值对我来说并不重要。更重要的是确保我不会在错误的地方意外地使用“错误”的一组值,因为这会导致以后的可读性变差。此外,大多数枚举具有相同的值(它们是寄存器集,或者实际上是偏移量,其中一些集执行相似)。所以主要关心的是可读性,这就是为什么我提到类型安全和“集群”它们的能力。
      • 听起来(再说一次,我们在这里几乎没有什么可合作的,对试图提供帮助的人来说,保密是一个挑战)你可能有一个问题,用特定领域的语言或其他语言可能会更好地解决更高层次的构造。我们的佣金系统是数据驱动的,而不是代码驱动的,因此我们可以运行我们的复杂规则(承销商、承运人、批发商、零售和各州法律规则),通过使用强制执行所有约束的编辑器页面使代码非常简单并确保可读性。
      • 嗯.. 保密当然不是我想做的。所以..我有一组数字。它们实际上映射到硬件上的寄存器。不同集合中的某些寄存器仍然可以以相同的方式访问,而其他寄存器则不能。因此,对于该功能,我想确保我必须从一组适当的寄存器中进行适当的输入。 (最后,所有都是数字,但它们有不同的含义——这就是我谈论类型安全和分组的原因。)
      • 所以,为了争论,假设我们谈论的是 R0-R15 的 ARM 处理器。这些寄存器被不同的操作码以不同的方式访问,那么你需要一个操作码/寄存器使用规则的映射吗?
      • 如果您想将所有内容保留在编译器本身中,我将支持@Servy 的建议(我已赞成)。否则,我建议您查看一种小型域特定语言,您可以在其中编码您的运营商/注册规则并为您的硬件提供直接支持。