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

别来无恙 提交于 2019-12-10 14:26:08

问题


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#?


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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.




回答5:


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]++;
}


来源:https://stackoverflow.com/questions/9435209/using-sentinal-values-in-c-sharp-enum-size-of-enum-at-compile-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!