如何把枚举类型的数量作为一个常量获取?

8

我从枚举中定义的项目总数中得知,我可以使用以下方法获取枚举数量:

Enum.GetNames(typeof(Item.Type)).Length;

效果很好!

但是,我需要将这个数字设为常量数字,这样我就可以在Unity的[Range(int, int)]功能中使用它。

private const int constEnumCount = Enum.GetNames(typeof(Item.Type)).Length;

以上方法无法生效,因为枚举的数量不是一个常数,所以我不能将它赋值给一个常量变量。

如何获取枚举的数量作为一个常数?


1
在定义为const时,您应该小心,因为该值需要在编译时使用,并且在编译时传播到所有引用的程序集中。是否可以改用readonly - Fabio Lima
@JoeBlow 这是开发人员在游戏测试中可以摆动数字的范围。它用于编辑引擎,以便在运行时进行代码更改。 - Evorlor
1
啊,你是指编辑器检查器属性!就像[Header("嗨,你好")]一样。不,你绝对不能以任何方式更改它。 - Fattie
哦,伙计,你想要做的事情的解决方案非常简单。我会给出一个答案。 - Fattie
6个回答

9

无法将枚举的数量作为const获取。 Enum.GetNames使用反射来获取这些信息,这本质上是运行时操作。因此,它无法在编译时知道,这是成为const的要求。


3

很遗憾,由于技术限制,无法以任何有意义的方式进行以下操作:

  • [Range(int,int)] 是一个属性,而所有提供给属性的信息都必须是const常量。
  • 获得枚举中值的数量的唯一真正可靠的方法是使用 Enum.GetValues(typeof(MyEnum)).LengthEnum.GetNames(typeof(MyEnum)).Length,这两个方法都是运行时反射。

然而,有一些技巧可以解决问题。例如,可以将枚举的值转换为整数。只要没有人明确为您的枚举定义值,您就可以使用枚举中的最后一个元素来实现以下操作:

[Range(0,(int)MyEnum.LastValue)]
public void BlahBlahBlah() {}

然而,要知道只要有人在使用的枚举后添加了新条目或重新排序枚举元素,你的代码将会出现不可预测的破坏,并且无法像你想要的那样运行。

很遗憾,C#编译器在编译时不能像Java、C和C++编译器一样进行简单的数学计算。因此,即使是我给出的示例也只有在LastValue没有被用于除了标记枚举中最后一个元素以外的任何内容时才能正常工作。这降低了C#编译器的复杂性,同时也大大提高了应用程序的编译速度。因此,C#和CLR团队做出了一些权衡来改善您在其他方面的体验。


这与C#编译器不够聪明无关。const被定义为常量。计算出的值则不是:如果枚举(例如在另一个程序集中定义)突然具有更多或更少的值,则需要更改计算出的值。这就是为什么该值需要在运行时计算的原因。 - Daniel Rose
@DanielRose,与C或Java相比,C#无法在初始化时计算const int myval = 3 + 5 + 8;,即使答案是一个常量。这就是我所指的。有些语言将在初始化时计算常量。您可以使用static readonly字段进行操作,但不能使用类/方法/参数属性。 - Berin Loritsch

1
假设您需要一个const,因为您正在尝试指定属性的值(this one?),那么您就没有运气了。
您的选择是:
  1. 在属性声明中硬编码计数或作为const本身,并小心保持计数与enum定义同步
  2. 使用PostSharp或其他面向方面的框架为您插入属性。我从未这样做过,但看起来很有可能(如何使用PostSharp属性注入属性?
您可能还可以通过T4模板某种方式来完成此操作,但这会变得非常笨拙。
如果是我的话,除非您谈论的是数百个不同的枚举集,否则我会硬编码长度,可能在需要时添加Assert。
// WARNING - if you add members update impacted Range attributes
public enum MyEnum
{
    foo,
    bar,
    baz
}

[Range(0, 3)]
class Test
{
    public Test()
    {
        int enumCount = Enum.GetNames(typeof(MyEnum)).Length;
        int rangeMax = GetType().GetCustomAttributes(typeof(Range), false).OfType<Range>().First().Max;

        Debug.Assert(enumCount == rangeMax);
    }
    static void Main(string[] args)
    {
        var test = new Test();
    }
}

1

我来晚了,但是我一直在使用一个巧妙的技巧来处理枚举默认值(即没有自定义值)。

public enum MyEnum {
    foo,
    bar,
    baz,
    count
}

由于枚举默认以0开始,确保最后一个条目仅命名为“count”,可以确保计数的值实际上是枚举本身中值的数量。

private const int constEnumCount = (int)MyEnum.count; 

干杯!


这可能会有所帮助,但它可能会导致大量代码来“忽略”最后一个项目。刚才我做了类似的事情,但只是使用了最后一个实际枚举值+1; - codah

0
在C#中,const被定义为常量,即该值(而不是产生它的计算!)直接嵌入到编译后的程序集中。
为了防止您做一些有问题的事情,编译器不允许您将计算结果分配给一个常量。要理解为什么,想象一下如果可能的话:
A.dll
  public enum Foo { A, B }

B.dll
  public const NumberOfFoos = Enum.GetNames(typeof(Foo)).Length;

// Compiles to:
B.dll
  public const NumberOfFoos = 2;

现在你改变了A.dll:

A.dll
  public enum Foo { A, B, C, D }

B.dll
      public const NumberOfFoos = 2; // Oh no!

因此,您需要重新编译 B.dll 以获取正确的值。

更糟糕的是:想象一下您有一个程序集 C.dll,它使用了 B.NumberOfFoos。值 2 将直接嵌入在 C.dll 中,因此在 B.dll 之后,还需要重新编译该程序集以获取正确的值。因此(成功的陷阱),C#不允许您将其分配给 const。


这有什么不同于将 public const int x = 5; 更改为 public const int x = 6 - Evorlor
@Evorlor 效果并没有区别。但是在你的情况下,当发生更改时相对明显,而与将计算结果分配给const相比较。 - Daniel Rose

0

你不能在运行时分配/创建一个const。这就是你正在尝试做的事情。一个const必须在编译时完全评估,而不是在运行时。

我不确定Unity (以及为什么它需要一个const),但我会尝试使用readonly

private readonly int constEnumCount = Enum.GetNames(typeof(Item.Type)).Length;

很遗憾,Unity不会接受这个只读。我想我应该问一个问题,为什么枚举的长度不能在编译时计算。 - Evorlor
2
作者尝试将此值用作属性参数,因此需要使用const。 - Grundy

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