Using an enum as an array index in C#

前端 未结 10 803
死守一世寂寞
死守一世寂寞 2020-12-28 14:17

I want to do the same as in this question, that is:

enum DaysOfTheWeek {Sunday=0, Monday, Tuesday...};
string[] message_array = new string[number_of_items_at         


        
相关标签:
10条回答
  • 2020-12-28 14:32

    It was a very good answer by @ian-goldby, but it didn't address the issue raised by @zar-shardan, which is an issue I hit myself. Below is my take on a solution, with a an extension class for converting an IEnumerable, and a test class below that:

    /// <summary>
    /// An array indexed by an enumerated type instead of an integer
    /// </summary>
    public class ArrayIndexedByEnum<TKey, TElement> : IEnumerable<TElement> where TKey : Enum
    {
      private readonly Array _array;
      private readonly Dictionary<TKey, TElement> _dictionary;
    
      /// <summary>
      /// Creates the initial array, populated with the defaults for TElement
      /// </summary>
      public ArrayIndexedByEnum()
      {
        var min = Convert.ToInt64(Enum.GetValues(typeof(TKey)).Cast<TKey>().Min());
        var max = Convert.ToInt64(Enum.GetValues(typeof(TKey)).Cast<TKey>().Max());
        var size = max - min + 1;
    
        // Check that we aren't creating a ridiculously big array, if we are,
        // then use a dictionary instead
        if (min >= Int32.MinValue && 
            max <= Int32.MaxValue && 
            size < Enum.GetValues(typeof(TKey)).Length * 3L)
        {
          var lowerBound = Convert.ToInt32(min);
          var upperBound = Convert.ToInt32(max);
          _array = Array.CreateInstance(typeof(TElement), new int[] {(int)size }, new int[] { lowerBound });
        }
        else
        {
          _dictionary = new Dictionary<TKey, TElement>();
          foreach (var value in Enum.GetValues(typeof(TKey)).Cast<TKey>())
          {
            _dictionary[value] = default(TElement);
          }
        }
      }
    
      /// <summary>
      /// Gets the element by enumerated type
      /// </summary>
      public TElement this[TKey key]
      {
        get => (TElement)(_array?.GetValue(Convert.ToInt32(key)) ?? _dictionary[key]);
        set
        {
          if (_array != null)
          {
            _array.SetValue(value, Convert.ToInt32(key));
          }
          else
          {
            _dictionary[key] = value;
          }
        }
      }
    
      /// <summary>
      /// Gets a generic enumerator
      /// </summary>
      public IEnumerator<TElement> GetEnumerator()
      {
        return Enum.GetValues(typeof(TKey)).Cast<TKey>().Select(k => this[k]).GetEnumerator();
      }
    
      System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
      {
        return GetEnumerator();
      }
    }
    

    Here's the extension class:

    /// <summary>
    /// Extensions for converting IEnumerable<TElement> to ArrayIndexedByEnum
    /// </summary>
    public static class ArrayIndexedByEnumExtensions
    {
      /// <summary>
      /// Creates a ArrayIndexedByEnumExtensions from an System.Collections.Generic.IEnumerable
      /// according to specified key selector and element selector functions.
      /// </summary>
      public static ArrayIndexedByEnum<TKey, TElement> ToArrayIndexedByEnum<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) where TKey : Enum
      {
        var array = new ArrayIndexedByEnum<TKey, TElement>();
        foreach(var item in source)
        {
          array[keySelector(item)] = elementSelector(item);
        }
        return array;
      }
      /// <summary>
      /// Creates a ArrayIndexedByEnum from an System.Collections.Generic.IEnumerable
      /// according to a specified key selector function.
      /// </summary>
      public static ArrayIndexedByEnum<TKey, TSource> ToArrayIndexedByEnum<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) where TKey : Enum
      {
        return source.ToArrayIndexedByEnum(keySelector, i => i);
      }
    }
    

    And here are my tests:

    [TestClass]
    public class ArrayIndexedByEnumUnitTest
    {
      private enum OddNumbersEnum : UInt16
      {
        One = 1,
        Three = 3,
        Five = 5,
        Seven = 7,
        Nine = 9
      }
    
      private enum PowersOf2 : Int64
      {
        TwoP0 = 1,
        TwoP1 = 2,
        TwoP2 = 4,
        TwoP3 = 8,
        TwoP4 = 16,
        TwoP5 = 32,
        TwoP6 = 64,
        TwoP7 = 128,
        TwoP8 = 256,
        TwoP9 = 512,
        TwoP10 = 1_024,
        TwoP11 = 2_048,
        TwoP12 = 4_096,
        TwoP13 = 8_192,
        TwoP14 = 16_384,
        TwoP15 = 32_768,
        TwoP16 = 65_536,
        TwoP17 = 131_072,
        TwoP18 = 262_144,
        TwoP19 = 524_288,
        TwoP20 = 1_048_576,
        TwoP21 = 2_097_152,
        TwoP22 = 4_194_304,
        TwoP23 = 8_388_608,
        TwoP24 = 16_777_216,
        TwoP25 = 33_554_432,
        TwoP26 = 67_108_864,
        TwoP27 = 134_217_728,
        TwoP28 = 268_435_456,
        TwoP29 = 536_870_912,
        TwoP30 = 1_073_741_824,
        TwoP31 = 2_147_483_648,
        TwoP32 = 4_294_967_296,
        TwoP33 = 8_589_934_592,
        TwoP34 = 17_179_869_184,
        TwoP35 = 34_359_738_368,
        TwoP36 = 68_719_476_736,
        TwoP37 = 137_438_953_472,
        TwoP38 = 274_877_906_944,
        TwoP39 = 549_755_813_888,
        TwoP40 = 1_099_511_627_776,
        TwoP41 = 2_199_023_255_552,
        TwoP42 = 4_398_046_511_104,
        TwoP43 = 8_796_093_022_208,
        TwoP44 = 17_592_186_044_416,
        TwoP45 = 35_184_372_088_832,
        TwoP46 = 70_368_744_177_664,
        TwoP47 = 140_737_488_355_328,
        TwoP48 = 281_474_976_710_656,
        TwoP49 = 562_949_953_421_312,
        TwoP50 = 1_125_899_906_842_620,
        TwoP51 = 2_251_799_813_685_250,
        TwoP52 = 4_503_599_627_370_500,
        TwoP53 = 9_007_199_254_740_990,
        TwoP54 = 18_014_398_509_482_000,
        TwoP55 = 36_028_797_018_964_000,
        TwoP56 = 72_057_594_037_927_900,
        TwoP57 = 144_115_188_075_856_000,
        TwoP58 = 288_230_376_151_712_000,
        TwoP59 = 576_460_752_303_423_000,
        TwoP60 = 1_152_921_504_606_850_000,
      }
    
      [TestMethod]
      public void TestSimpleArray()
      {
        var array = new ArrayIndexedByEnum<OddNumbersEnum, string>();
    
        var odds = Enum.GetValues(typeof(OddNumbersEnum)).Cast<OddNumbersEnum>().ToList();
    
        // Store all the values
        foreach (var odd in odds)
        {
          array[odd] = odd.ToString();
        }
    
        // Check the retrieved values are the same as what was stored
        foreach (var odd in odds)
        {
          Assert.AreEqual(odd.ToString(), array[odd]);
        }
      }
    
      [TestMethod]
      public void TestPossiblyHugeArray()
      {
        var array = new ArrayIndexedByEnum<PowersOf2, string>();
    
        var powersOf2s = Enum.GetValues(typeof(PowersOf2)).Cast<PowersOf2>().ToList();
    
        // Store all the values
        foreach (var powerOf2 in powersOf2s)
        {
          array[powerOf2] = powerOf2.ToString();
        }
    
        // Check the retrieved values are the same as what was stored
        foreach (var powerOf2 in powersOf2s)
        {
          Assert.AreEqual(powerOf2.ToString(), array[powerOf2]);
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-28 14:34

    Since C# 7.3 it has been possible to use System.Enum as a constraint on type parameters. So the nasty hacks in the some of the other answers are no longer required.

    Here's a very simple ArrayByEum class that does exactly what the question asked.

    Note that it will waste space if the enum values are non-contiguous, and won't cope with enum values that are too large for an int. I did say this example was very simple.

    /// <summary>An array indexed by an Enum</summary>
    /// <typeparam name="T">Type stored in array</typeparam>
    /// <typeparam name="U">Indexer Enum type</typeparam>
    public class ArrayByEnum<T,U> : IEnumerable where U : Enum // requires C# 7.3 or later
    {
      private readonly T[] _array;
      private readonly int _lower;
    
      public ArrayByEnum()
      {
        _lower = Convert.ToInt32(Enum.GetValues(typeof(U)).Cast<U>().Min());
        int upper = Convert.ToInt32(Enum.GetValues(typeof(U)).Cast<U>().Max());
        _array = new T[1 + upper - _lower];
      }
    
      public T this[U key]
      {
        get { return _array[Convert.ToInt32(key) - _lower]; }
        set { _array[Convert.ToInt32(key) - _lower] = value; }
      }
    
      public IEnumerator GetEnumerator()
      {
        return Enum.GetValues(typeof(U)).Cast<U>().Select(i => this[i]).GetEnumerator();
      }
    }
    

    Usage:

    ArrayByEnum<string,MyEnum> myArray = new ArrayByEnum<string,MyEnum>();
    myArray[MyEnum.First] = "Hello";
    
    myArray[YourEnum.Other] = "World"; // compiler error
    
    0 讨论(0)
  • 2020-12-28 14:40

    Here you go:

    string[] message_array = Enum.GetNames(typeof(DaysOfTheWeek));
    

    If you really need the length, then just take the .Length on the result :) You can get values with:

    string[] message_array = Enum.GetValues(typeof(DaysOfTheWeek));
    
    0 讨论(0)
  • 2020-12-28 14:49

    I realize this is an old question, but there have been a number of comments about the fact that all solutions so far have run-time checks to ensure the data type is an enum. Here is a complete solution (with some examples) of a solution with compile time checks (as well as some comments and discussions from my fellow developers)

    //There is no good way to constrain a generic class parameter to an Enum.  The hack below does work at compile time,
    //  though it is convoluted.  For examples of how to use the two classes EnumIndexedArray and ObjEnumIndexedArray,
    //  see AssetClassArray below.  Or, e.g.
    //      EConstraint.EnumIndexedArray<int, YourEnum> x = new EConstraint.EnumIndexedArray<int, YourEnum>();
    //  See this post 
    //      http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum/29581813#29581813
    // and the answer/comments by Julien Lebosquain
    public class EConstraint : HackForCompileTimeConstraintOfTEnumToAnEnum<System.Enum> { }//THIS MUST BE THE ONLY IMPLEMENTATION OF THE ABSTRACT HackForCompileTimeConstraintOfTEnumToAnEnum
    public abstract class HackForCompileTimeConstraintOfTEnumToAnEnum<SystemEnum> where SystemEnum : class
    {
        //For object types T, users should use EnumIndexedObjectArray below.
        public class EnumIndexedArray<T, TEnum>
            where TEnum : struct, SystemEnum
        {
            //Needs to be public so that we can easily do things like intIndexedArray.data.sum()
            //   - just not worth writing up all the equivalent methods, and we can't inherit from T[] and guarantee proper initialization.
            //Also, note that we cannot use Length here for initialization, even if Length were defined the same as GetNumEnums up to
            //  static qualification, because we cannot use a non-static for initialization here.
            //  Since we want Length to be non-static, in keeping with other definitions of the Length property, we define the separate static
            //  GetNumEnums, and then define the non-static Length in terms of the actual size of the data array, just for clarity,
            //  safety and certainty (in case someone does something stupid like resizing data).
            public T[] data = new T[GetNumEnums()];
    
            //First, a couple of statics allowing easy use of the enums themselves.
            public static TEnum[] GetEnums()
            {
                return (TEnum[])Enum.GetValues(typeof(TEnum));
            }
            public TEnum[] getEnums()
            {
                return GetEnums();
            }
            //Provide a static method of getting the number of enums.  The Length property also returns this, but it is not static and cannot be use in many circumstances.
            public static int GetNumEnums()
            {
                return GetEnums().Length;
            }
            //This should always return the same as GetNumEnums, but is not static and does it in a way that guarantees consistency with the member array.
            public int Length { get { return data.Length; } }
            //public int Count  { get { return data.Length; } }
    
            public EnumIndexedArray() { }
    
            // [WDS 2015-04-17] Remove. This can be dangerous. Just force people to use EnumIndexedArray(T[] inputArray).
            // [DIM 2015-04-18] Actually, if you think about it, EnumIndexedArray(T[] inputArray) is just as dangerous:
            //   For value types, both are fine.  For object types, the latter causes each object in the input array to be referenced twice,
            //   while the former causes the single object t to be multiply referenced.  Two references to each of many is no less dangerous
            //   than 3 or more references to one. So all of these are dangerous for object types.
            //   We could remove all these ctors from this base class, and create a separate
            //         EnumIndexedValueArray<T, TEnum> : EnumIndexedArray<T, TEnum> where T: struct ...
            //   but then specializing to TEnum = AssetClass would have to be done twice below, once for value types and once
            //   for object types, with a repetition of all the property definitions.  Violating the DRY principle that much
            //   just to protect against stupid usage, clearly documented as dangerous, is not worth it IMHO.
            public EnumIndexedArray(T t)
            {
                int i = Length;
                while (--i >= 0)
                {
                    this[i] = t;
                }
            }
            public EnumIndexedArray(T[] inputArray)
            {
                if (inputArray.Length > Length)
                {
                    throw new Exception(string.Format("Length of enum-indexed array ({0}) to big. Can't be more than {1}.", inputArray.Length, Length));
                }
                Array.Copy(inputArray, data, inputArray.Length);
            }
            public EnumIndexedArray(EnumIndexedArray<T, TEnum> inputArray)
            {
                Array.Copy(inputArray.data, data, data.Length);
            }
    
            //Clean data access
            public T this[int ac] { get { return data[ac]; } set { data[ac] = value; } }
            public T this[TEnum ac] { get { return data[Convert.ToInt32(ac)]; } set { data[Convert.ToInt32(ac)] = value; } }
        }
    
    
        public class EnumIndexedObjectArray<T, TEnum> : EnumIndexedArray<T, TEnum>
            where TEnum : struct, SystemEnum
            where T : new()
        {
            public EnumIndexedObjectArray(bool doInitializeWithNewObjects = true)
            {
                if (doInitializeWithNewObjects)
                {
                    for (int i = Length; i > 0; this[--i] = new T()) ;
                }
            }
            // The other ctor's are dangerous for object arrays
        }
    
        public class EnumIndexedArrayComparator<T, TEnum> : EqualityComparer<EnumIndexedArray<T, TEnum>>
            where TEnum : struct, SystemEnum
        {
            private readonly EqualityComparer<T> elementComparer = EqualityComparer<T>.Default;
    
            public override bool Equals(EnumIndexedArray<T, TEnum> lhs, EnumIndexedArray<T, TEnum> rhs)
            {
                if (lhs == rhs)
                    return true;
                if (lhs == null || rhs == null)
                    return false;
    
                //These cases should not be possible because of the way these classes are constructed.
                // HOWEVER, the data member is public, so somebody _could_ do something stupid and make 
                // data=null, or make lhs.data == rhs.data, even though lhs!=rhs (above check)
                //On the other hand, these are just optimizations, so it won't be an issue if we reomve them anyway,
                // Unless someone does something really dumb like setting .data to null or resizing to an incorrect size,
                // in which case things will crash, but any developer who does this deserves to have it crash painfully...
                //if (lhs.data == rhs.data)
                //    return true;
                //if (lhs.data == null || rhs.data == null)
                //    return false;
    
                int i = lhs.Length;
                //if (rhs.Length != i)
                //    return false;
                while (--i >= 0)
                {
                    if (!elementComparer.Equals(lhs[i], rhs[i]))
                        return false;
                }
                return true;
            }
            public override int GetHashCode(EnumIndexedArray<T, TEnum> enumIndexedArray)
            {
                //This doesn't work: for two arrays ar1 and ar2, ar1.GetHashCode() != ar2.GetHashCode() even when ar1[i]==ar2[i] for all i (unless of course they are the exact same array object)
                //return engineArray.GetHashCode();
                //Code taken from comment by Jon Skeet - of course - in http://stackoverflow.com/questions/7244699/gethashcode-on-byte-array
                //31 and 17 are used commonly elsewhere, but maybe because everyone is using Skeet's post.
                //On the other hand, this is really not very critical.
                unchecked
                {
                    int hash = 17;
                    int i = enumIndexedArray.Length;
                    while (--i >= 0)
                    {
                        hash = hash * 31 + elementComparer.GetHashCode(enumIndexedArray[i]);
                    }
                    return hash;
                }
            }
        }
    }
    
    //Because of the above hack, this fails at compile time - as it should.  It would, otherwise, only fail at run time.
    //public class ThisShouldNotCompile : EConstraint.EnumIndexedArray<int, bool>
    //{
    //}
    
    //An example
    public enum AssetClass { Ir, FxFwd, Cm, Eq, FxOpt, Cr };
    public class AssetClassArrayComparator<T> : EConstraint.EnumIndexedArrayComparator<T, AssetClass> { }
    public class AssetClassIndexedArray<T> : EConstraint.EnumIndexedArray<T, AssetClass>
    {
        public AssetClassIndexedArray()
        {
        }
        public AssetClassIndexedArray(T t) : base(t)
        {
        }
        public AssetClassIndexedArray(T[] inputArray) :  base(inputArray)
        {
        }
        public AssetClassIndexedArray(EConstraint.EnumIndexedArray<T, AssetClass> inputArray) : base(inputArray)
        {
        }
    
        public T Cm    { get { return this[AssetClass.Cm   ]; } set { this[AssetClass.Cm   ] = value; } }
        public T FxFwd { get { return this[AssetClass.FxFwd]; } set { this[AssetClass.FxFwd] = value; } }
        public T Ir    { get { return this[AssetClass.Ir   ]; } set { this[AssetClass.Ir   ] = value; } }
        public T Eq    { get { return this[AssetClass.Eq   ]; } set { this[AssetClass.Eq   ] = value; } }
        public T FxOpt { get { return this[AssetClass.FxOpt]; } set { this[AssetClass.FxOpt] = value; } }
        public T Cr    { get { return this[AssetClass.Cr   ]; } set { this[AssetClass.Cr   ] = value; } }
    }
    
    //Inherit from AssetClassArray<T>, not EnumIndexedObjectArray<T, AssetClass>, so we get the benefit of the public access getters and setters above
    public class AssetClassIndexedObjectArray<T> : AssetClassIndexedArray<T> where T : new()
    {
        public AssetClassIndexedObjectArray(bool bInitializeWithNewObjects = true)
        {
            if (bInitializeWithNewObjects)
            {
                for (int i = Length; i > 0; this[--i] = new T()) ;
            }
        }
    }
    

    EDIT: If you are using C# 7.3 or later, PLEASE don't use this ugly solution. See Ian Goldby's answer from 2018.

    0 讨论(0)
提交回复
热议问题