简体   繁体   中英

Using sentinal values in C# enum (size of enum at compile time)?

A shortcut I often use in C when dealing with embedded APIs (communications protocols, primarily) allows me to edit an enum array and have everything else sized correctly after that:

typedef enum {
   errortype1,
   errortype2,
   ...
   errortypeN,
   ERROR_TYPE_MAX
} ErrorTypeList;

int errorcounts[ERROR_TYPE_MAX]; // Create array to hold a counter for each error type

As long as I add new error types before ERROR_TYPE_MAX then I can use that value everywhere else to give me the size of the enum.

In C#, however, this doesn't work as-is:

enum ErrorTypeList {
   errortype1,
   errortype2,
   ...
   errortypeN,
   ERROR_TYPE_MAX
};

int errorcounts[ErrorTypeList.ERROR_TYPE_MAX]; // Create array to hold a counter for each error type

This usage presents the error Array size cannot be specified in a variable declaration which suggests using new .

Is defining the array at runtime (via new ) my only option, or is there a way to accomplish this without a new, since the size of the enum isn't going to change after compilation?

Is there an alternative to this type of enum sizing pattern that better fits into c#?

If you want the size of an enum, you can do this:

enum Foo { A , B , C , ... , }
...
int[] someArray = new int[ sizeof(Foo) ] ;

But that won't give you what you want, since sizeof returns the size of the object in octets (4 in this case, since an enum, by default wraps an Int32).

To get the number of values in the enum, do something like this:

enum Foo { A , B , C , ... , }
...
int[] someArray = new int[ Enum.GetValues(typeof(Foo)).Length ] ;

If you're planning on using the value of the enum to index into the array, you should note that this makes the [unwarranted] assumption that the enum's values start at 0 and increment by 1: a subsequent change to the enum's definition that violates the assumption will break things.

Further, if the enum has the [Flags] attribute applied to it, then the domain of the enum is potentially [much] larger than the number of discrete values defined.

You would be better off to use a dictionary, thus:

[Flags]
enum Foo { Unknown = 0 , A = 1 , B = 2 , C = 4 , D = 8 , E = 16 , ... , }
...
Dictionary<Foo,SomeType> myDictionary = new Dictionary<Foo,SomeType>() ;

Now your code is much more flexible: you don't care about how the internals of the enum are arranged.

You might even pre-populate the dictionary with the expected set of keys (and throw an exception if handed a value other than the expected).

Edited To Note:

You could do something like this as well:

void DoSomething()
{
  int[] someArray = CreateArrayBasedOnFoo<int>() ;
  ...
  return ;
}

public T[] CreateArrayBasedOnFoo<T>()
{
  Foo[] values = (Foo[]) Enum.GetValues(typeof(Foo)) ;
  int   lo     = (int) values.Min() ;
  int   hi     = (int) values.Max() ;
  int  domain  = ( hi - lo ) + 1 ;
  T[] instance = (T[]) Array.CreateInstance( typeof(T), new int[]{domain} , new int[]{lo} ) ;

  return instance ;
}

The CreateArrayBasedOnFoo<T>() method will hand back an array sized to fit the enum and whose lower bound is the smallest value of the enum. For instance, if you enum has 3 discrete values: 3, 5 and 7, the array handed back will be 5 elements in length and will have a lower bound of 3.

You will have to use new .

A variable/field declaration does not specify the size of the array, that's only accomplished when you construct the array instance.

At most you can specify how many dimensions the array will have, but not the size of each dimension.

You can only define fixed length arrays in an unsafe struct . Otherwise C# only supports creating arrays at run-time using new .

Since arrays are a reference type and stored on the heap, this should not make any performance difference.

To answer the second part of your question, you can get the number of entries in your enum by using the Enum class, like this:

enum ErrorTypeList {
   errortype1,
   errortype2,
   ...
   errortypeN // ERROR_TYPE_MAX is not necessary
};


int errorCounts[] = new int[Enum.GetNames(typeof(ErrorTypeList)).Length];

Note that the trick with counters in an array works only when the enum values are consecutive. This is somewhat more fragile than a Dictionary<ErrorTypeList,int> , which would work even if you decide to assign your enumeration constants non-consecutive or negative values. Of course you would need to initialize the counts to zero at the beginning to avoid the "element is not there" exception at runtime.

Because of enum entry initializers, I think it is an unsafe practice to use the number of enum entries perform bounds checking on an enum value in C#.

There are ample validation methods such as Enum.IsDefined() and Enum.ToObject() to allow you to more safely validate and convert numeric values to type specific enum entries.

Please see the MSDN reference on the Enum class:

http://msdn.microsoft.com/en-us/library/system.enum.aspx

Edited to address clarified problem statement...

I think a dictionary mapping enum values and integer counters would better serve your needs. It will still work if somebody sticks an initializer on one or more of your enum entries.

enum MyEnum {a=32, b, c, d=48, e, f}

Dictionary<MyEnum, int>ErrorCounts = new Dictionary<MyEnum, int>();

and if you really want to pre-initialize it:

foreach(MyEnum me in Enum.GetValues(typeof(MyEnum)))
{
    ErrorCounts[me] = 0;
}

otherwise, I would probably only add entries as the error condition occurs

private void IncrementErrorCount(MyEnum val)
{
    if(ErrorCounts[val] == null)
        ErrorCounts[val]=1
    else
        ErrorCounts[val]++;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM