8

From Total number of items defined in an enum, I see I can get the number of enums by using:

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

Works great!

But, I need this number as a constant number, so that I can use it in Unity's [Range(int, int)] feature.

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

The above does not work, because the enum count is not a constant number, so I cannot assign it to a constant variable.

How can I get the number of enums as a constant?

Community
  • 1
  • 1
Evorlor
  • 7,263
  • 17
  • 70
  • 141
  • 1
    You should be careful when defining this as a `const`, because the value is need at compile time, and the value when compiling will be propagated in all referenced assemblies. Is it possible to use readonly instead? – Fabio Lima Apr 05 '16 at 17:09
  • @JoeBlow It is the range that the developer can swing a number between for game testing. It is for editing the engine so that changes can be made to the code before and during runtime. – Evorlor Apr 05 '16 at 17:15
  • 1
    Ah you mean the EDITOR INSPECTOR ATTRIBUTE! Much like [Header("Hi there")]. No, you absolutely can't change that in any way. – Fattie Apr 05 '16 at 19:30
  • lol dude the solution to what you are trying to do is incredibly simple. I'll put in an answer – Fattie Apr 05 '16 at 19:32

6 Answers6

9

It's not possible to get the number of enums as a const. Enum.GetNames uses reflection to get these things, and that's inherently a runtime operation. Therefore, it can't be known at compile time, which is a requirement for being const.

Almo
  • 15,538
  • 13
  • 67
  • 95
3

Unfortunately, this is not possible to do in any meaningful way due to technical limitations:

  • [Range(int,int)] is an attribute, and all information provided to an attribute has to be a const
  • The only truly bulletproof way to get the number of values in an enum is to use Enum.GetValues(typeof(MyEnum)).Length or the Enum.GetNames(typeof(MyEnum)).Length, both of which are run time reflection.

However, there are hacks that can sort of work. For example, the value of an enum can be cast to an integer. As long as nobody is explicitly defining values for your enums you can use the last element in your enum kind of like this:

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

However, know that as soon as someone adds a new entry to the enum after the one you are using or reorders the elements in the enum, your code will break unpredictably and not behave like you want.

It's sad, but the C# compiler is not smart enough to do simple math in the compiler like Java, C, and C++ compilers. So even the example I gave would only really work if the LastValue wasn't ever used for anything except to mark the last element in the enum. It lowers the complexity of the C# compiler, which also greatly improves the compilation speed for your application. As such, there are some trade-offs that the C# and CLR team took to improve your experience in other places.

Berin Loritsch
  • 11,400
  • 4
  • 30
  • 57
  • This has nothing to do with the C# compiler not being smart enough. A `const` is defined as _constant_. A calculated value is not: If the enum (ex. defined in a different assembly) suddenly has more or less values, the calculated value would need to change. That's why the value needs to be calculated at runtime. – Daniel Rose Nov 13 '17 at 11:56
  • @DanielRose, In contrast to C or Java, C# can't evaluate (at the time of writing the answer) `const int myval = 3 + 5 + 8;` even though the answer is a _constant_. That is what I was referring to. Some languages will evaluate the constant at initialization time. You can work with `static readonly` fields, but not with class/method/parameter attributes. – Berin Loritsch Nov 13 '17 at 14:12
1

Assuming that you need a const because you are trying to specify the values for an Attribute (this one?) then you are out of luck.

Your options are:

  1. Hardcode the count in the attribute declaration or as a const itself and be careful to keep the count in sync with the enum definition
  2. Use PostSharp or some other aspect oriented framework to insert the attribute for you. Have never done this myself but it looks possible (How to inject an attribute using a PostSharp attribute?)

You could probably also finagle some way to so this with T4 templates but that would get excessively kludgy.

Were it me, unless you are talking but hundreds of different set of enumerations, I'd hardcode the length and maybe add an Assert where I needed it.

// 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();
    }
}
Community
  • 1
  • 1
dkackman
  • 15,179
  • 13
  • 69
  • 123
1

Super late to the game here but I've always used a nifty hack when using enum defaults (i.e. no custom values).

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

Since enums default to starting with 0, ensuring the last entry is named simply 'count' ensures that the value of count is in fact the number of values in the enum itself.

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

Cheers!

Robert French
  • 686
  • 4
  • 12
  • This can be helpful but it can lead to a lot of code to 'ignore' the last item. Just now I did something similar but simply used the last actual enum + 1; – codah Jun 23 '21 at 04:25
0

You cannot assign/create a const at runtime. That is what you are trying to do. A const has to be fully evaluated at compile time, not runtime.

I am not sure about Unity (and why it requires a const), but I would look for using readonly

private readonly int constEnumCount = Enum.GetNames(typeof(Item.Type)).Length;
Habib
  • 219,104
  • 29
  • 407
  • 436
  • Unfortunately, Unity will not accept a readonly for this. I guess I should ask the question of why the length of an enum cannot be calculated at compile time. – Evorlor Apr 05 '16 at 17:14
  • 2
    author try use this value as attribute parameter, that's why it need const – Grundy Apr 05 '16 at 17:15
0

In C#, a const is defined as constant, i.e. the value (not the calculation which produced it!) is directly embedded in the compiled assembly.

To stop you from doing something problematic, the compiler does not allow you to assign the result of a calculation to a const. To understand why, imagine it were possible:

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;

Now you change A.dll:

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

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

So you would need to recompile B.dll to get the correct value.

It gets worse: Imagine you had an assembly C.dll, which uses B.NumberOfFoos. The value 2 would be directly embedded in C.dll, so it also would need to be recompiled, after B.dll, in order to get the correct value. Therefore (pit of success), C# does not allow you to do that assignment to a const.

Daniel Rose
  • 17,233
  • 9
  • 65
  • 88
  • How is this any different from changing `public const int x = 5;` to `public const int x = 6`? – Evorlor Nov 13 '17 at 14:20
  • @Evorlor The effect is not different. But in your case it is (relatively) obvious when a change happens, compared to assigning the result of a calculation to a const. – Daniel Rose Nov 15 '17 at 09:36