【问题标题】:Using an enum as an array index in C#在 C# 中使用枚举作为数组索引
【发布时间】:2010-11-02 04:17:45
【问题描述】:

我想做和this question一样的事情,也就是:

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

...

Console.Write(custom_array[(int)DaysOfTheWeek.Sunday]);

然而,我宁愿有一些不可或缺的东西,而不是编写这个容易出错的代码。是否有 C# 中的内置模块可以做到这一点?

【问题讨论】:

  • 关于你的名字“DaysOfTheWeek”的小评论:C# 标准规定非标志样式的枚举应该有单数名称,而标志样式的枚举应该有复数名称,所以“DayOfTheWeek”会更好。 msdn.microsoft.com/en-us/library/ms229040.aspx

标签: c# indexing enums


【解决方案1】:

如果您的枚举项的值是连续的,则数组方法工作得很好。但是,无论如何,您都可以使用Dictionary<DayOfTheWeek, string>(顺便说一下,它的性能较差)。

【讨论】:

  • 那会对性能产生重大影响?怎么会?
  • @Spencer:字典查找比直接数组索引(或列表索引)慢得多。如果你经常这样做,它可能会对性能产生显着影响。
  • 是的,这是我为 MouseButton 选择的解决方案,因为它是一个标志枚举(又名 0001 右侧,0010 中间,0100 左侧等)。尽管如此,对于这么简单的事情来说还是很丑陋的。
  • @Spencer:我没有说“重要”。这是否重要取决于您的具体用法。是不是比较慢?是的,毫无疑问,字典比直接数组查找要慢。意义重大吗?您应该进行基准测试并查看。
  • 如果不能证明有显着影响,最好使用字典解决方案。对数组进行预优化会产生两个问题:首先,它放弃了枚举的类型安全性,使其实际上与静态类中定义的常量没有什么不同(但更做作)。其次,你最终会编写更多而不是更少的代码,以便让它按照你想要的方式运行。
【解决方案2】:

从 C# 7.3 开始,可以使用 System.Enum as a constraint on type parameters。因此,不再需要其他一些答案中令人讨厌的黑客攻击。

这是一个非常简单的 ArrayByEum 类,它完全按照问题的要求完成。

请注意,如果枚举值不连续,它将浪费空间,并且无法处理对于int 来说太大的枚举值。我确实说过这个例子很简单。

/// <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();
  }
}

用法:

ArrayByEnum<string,MyEnum> myArray = new ArrayByEnum<string,MyEnum>();
myArray[MyEnum.First] = "Hello";

myArray[YourEnum.Other] = "World"; // compiler error

【讨论】:

  • 您的回答效果很好。它提供对数组中元素的写访问权限。另一个通用答案(@Matthew Whited 的 Caster 类)提供只读,因此您必须使用实例初始化数组,然后您无法更改实例(仅写入其中)。 MyTable[Seat.East] = new Player { Name = "Joe", ID = 1234 };但是他的 Caster 反序列化更好。我在您的解决方案中遇到了反序列化(NewtonSoft)的异常问题。
【解决方案3】:

您可以创建一个可以为您完成工作的类或结构


public class Caster
{
    public enum DayOfWeek
    {
        Sunday = 0,
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday
    }

    public Caster() {}
    public Caster(string[] data) { this.Data = data; }

    public string this[DayOfWeek dow]{
        get { return this.Data[(int)dow]; }
    }

    public string[] Data { get; set; }


    public static implicit operator string[](Caster caster) { return caster.Data; }
    public static implicit operator Caster(string[] data) { return new Caster(data); }

}

class Program
{
    static void Main(string[] args)
    {
        Caster message_array = new string[7];
        Console.Write(message_array[Caster.DayOfWeek.Sunday]);
    }
}

编辑

由于缺少更好的放置位置,我在下面发布了 Caster 类的通用版本。不幸的是,它依赖运行时检查将 TKey 强制为枚举。

public enum DayOfWeek
{
    Weekend,
    Sunday = 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

public class TypeNotSupportedException : ApplicationException
{
    public TypeNotSupportedException(Type type)
        : base(string.Format("The type \"{0}\" is not supported in this context.", type.Name))
    {
    }
}

public class CannotBeIndexerException : ApplicationException
{
    public CannotBeIndexerException(Type enumUnderlyingType, Type indexerType)
        : base(
            string.Format("The base type of the enum (\"{0}\") cannot be safely cast to \"{1}\".",
                          enumUnderlyingType.Name, indexerType)
            )
    {
    }
}

public class Caster<TKey, TValue>
{
    private readonly Type baseEnumType;

    public Caster()
    {
        baseEnumType = typeof(TKey);
        if (!baseEnumType.IsEnum)
            throw new TypeNotSupportedException(baseEnumType);
    }

    public Caster(TValue[] data)
        : this()
    {
        Data = data;
    }

    public TValue this[TKey key]
    {
        get
        {
            var enumUnderlyingType = Enum.GetUnderlyingType(baseEnumType);
            var intType = typeof(int);
            if (!enumUnderlyingType.IsAssignableFrom(intType))
                throw new CannotBeIndexerException(enumUnderlyingType, intType);
            var index = (int) Enum.Parse(baseEnumType, key.ToString());
            return Data[index];
        }
    }

    public TValue[] Data { get; set; }


    public static implicit operator TValue[](Caster<TKey, TValue> caster)
    {
        return caster.Data;
    }

    public static implicit operator Caster<TKey, TValue>(TValue[] data)
    {
        return new Caster<TKey, TValue>(data);
    }
}

// declaring and using it.
Caster<DayOfWeek, string> messageArray =
    new[]
        {
            "Sunday",
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday"
        };
Console.WriteLine(messageArray[DayOfWeek.Sunday]);
Console.WriteLine(messageArray[DayOfWeek.Monday]);
Console.WriteLine(messageArray[DayOfWeek.Tuesday]);
Console.WriteLine(messageArray[DayOfWeek.Wednesday]);
Console.WriteLine(messageArray[DayOfWeek.Thursday]);
Console.WriteLine(messageArray[DayOfWeek.Friday]);
Console.WriteLine(messageArray[DayOfWeek.Saturday]);

【讨论】:

  • +1,这至少概括了试图将枚举硬塞到做一些它不是设计用来(也不应该用来)做的事情的痛苦。
  • 好吧,我同意应该为二传手做一些更理智的事情。但我永远不会把它概括为“不应该使用”的东西。另一种选择是创建以类似方式使用的自定义只读结构。
  • @Matthew Whited,当我说“不应该使用”时,我可能反应过度了,但我个人从未见过也无法想象创建枚举的正当理由仅用作数组中的索引器。我可以自信地说,在这种有限的情况下,你失去了枚举的所有好处,却一无所获。
  • 作为练习,您可以尝试使您的解决方案通用。我认为这是可以做到的,并且会让它踢得更多@$$。
  • @MichaelMeadows,如果你有太阳系物体(太阳、地球、月亮等),当你硬编码它们的属性(名称、质量、位置、速度、等等。)?与其通过 0、1、2 等访问数组,不如通过 Sun、Earth、Moon 等访问它们会有所帮助。什么是更优雅的解决方案?请记住,这些值可以硬编码,因为它们本质上是常量。
【解决方案4】:

枚举的紧凑形式用作索引并将任何类型分配给字典 和强类型。在这种情况下,会返回浮点值,但值可能是具有属性和方法等的复杂类实例:

enum opacityLevel { Min, Default, Max }
private static readonly Dictionary<opacityLevel, float> _oLevels = new Dictionary<opacityLevel, float>
{
    { opacityLevel.Max, 40.0 },
    { opacityLevel.Default, 50.0 },
    { opacityLevel.Min, 100.0 }
};

//Access float value like this
var x = _oLevels[opacitylevel.Default];

【讨论】:

    【解决方案5】:

    给你:

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

    如果你真的需要长度,那么只需在结果上加上 .Length :) 您可以通过以下方式获取值:

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

    【讨论】:

      【解决方案6】:

      如果您实际上只需要一张地图,但又不想产生与字典查找相关的性能开销,那么这可能会奏效:

          public class EnumIndexedArray<TKey, T> : IEnumerable<KeyValuePair<TKey, T>> where TKey : struct
          {
              public EnumIndexedArray()
              {
                  if (!typeof (TKey).IsEnum) throw new InvalidOperationException("Generic type argument is not an Enum");
                  var size = Convert.ToInt32(Keys.Max()) + 1;
                  Values = new T[size];
              }
      
              protected T[] Values;
      
              public static IEnumerable<TKey> Keys
              {
                  get { return Enum.GetValues(typeof (TKey)).OfType<TKey>(); }
              }
      
              public T this[TKey index]
              {
                  get { return Values[Convert.ToInt32(index)]; }
                  set { Values[Convert.ToInt32(index)] = value; }
              }
      
              private IEnumerable<KeyValuePair<TKey, T>> CreateEnumerable()
              {
                  return Keys.Select(key => new KeyValuePair<TKey, T>(key, Values[Convert.ToInt32(key)]));
              }
      
              public IEnumerator<KeyValuePair<TKey, T>> GetEnumerator()
              {
                  return CreateEnumerable().GetEnumerator();
              }
      
              IEnumerator IEnumerable.GetEnumerator()
              {
                  return GetEnumerator();
              }
          }
      

      因此,在您的情况下,您可以得出:

      class DaysOfWeekToStringsMap:EnumIndexedArray<DayOfWeek,string>{};
      

      用法:

      var map = new DaysOfWeekToStringsMap();
      
      //using the Keys static property
      foreach(var day in DaysOfWeekToStringsMap.Keys){
          map[day] = day.ToString();
      }
      foreach(var day in DaysOfWeekToStringsMap.Keys){
          Console.WriteLine("map[{0}]={1}",day, map[day]);
      }
      
      // using iterator
      foreach(var value in map){
          Console.WriteLine("map[{0}]={1}",value.Key, value.Value);
      }
      

      显然这个实现是由一个数组支持的,所以不连续的枚举是这样的:

      enum
      {
        Ok = 1,
        NotOk = 1000000
      }
      

      会导致内存使用过多。

      如果您需要最大可能的性能,您可能希望使其不那么通用并松散我必须使用的所有通用枚举处理代码才能使其编译和工作。不过我没有对此进行基准测试,所以也许没什么大不了的。

      缓存 Keys 静态属性也可能有所帮助。

      【讨论】:

        【解决方案7】:

        我意识到这是一个老问题,但有许多 cmets 表示,到目前为止,所有解决方案都具有运行时检查以确保数据类型是枚举。这是带有编译时检查的解决方案的完整解决方案(带有一些示例)(以及来自我的开发人员的一些 cmet 和讨论)

        //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()) ;
                }
            }
        }
        

        编辑: 如果您使用的是 C# 7.3 或更高版本,请不要使用这种丑陋的解决方案。请参阅 Ian Goldby 2018 年的回答。

        【讨论】:

          【解决方案8】:

          您总是可以做一些额外的映射,以一致且定义的方式获取枚举值的数组索引:

          int ArrayIndexFromDaysOfTheWeekEnum(DaysOfWeek day)
          {
             switch (day)
             {
               case DaysOfWeek.Sunday: return 0;
               case DaysOfWeek.Monday: return 1;
               ...
               default: throw ...;
             }
          }
          

          尽可能具体。有一天有人会修改你的枚举,代码会失败,因为枚举的值被(错误)用作数组索引。

          【讨论】:

          • 在这种情况下,仅在 Enum 定义本身中指定值是有意义的。
          • @van,你对这个案例是对的,但@David Humpohl 关于代码最终可能会失败的断言有一些优点。在 DaysOfWeek 的情况下,可能性很小,但基于业务价值的枚举可能会发生变化,从而导致基础价值发生变化。
          【解决方案9】:

          以上问题可以总结如下:

          我来自 Delphi,您可以在其中定义一个数组,如下所示:

          type
            {$SCOPEDENUMS ON}
            TDaysOfTheWeek = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
          
            TDaysOfTheWeekStrings = array[TDaysOfTheWeek];
          

          然后你可以使用 Min 和 Max 遍历数组:

          for Dow := Min(TDaysOfTheWeek) to Max(TDaysOfTheWeek) 
            DaysOfTheWeekStrings[Dow] := '';
          

          虽然这是一个非常人为的示例,但当您稍后在代码中处理数组位置时,我只需输入DaysOfTheWeekStrings[TDaysOfTheWeek.Monday]。这样做的好处是我应该 TDaysOfTheWeek 增加大小然后我不必记住数组的新大小等......但是回到 C# 世界。我找到了这个例子C# Enum Array Example

          【讨论】:

            【解决方案10】:

            @ian-goldby 的回答非常好,但没有解决@zar-shardan 提出的问题,这是我自己遇到的问题。下面是我对解决方案的看法,其中包含一个用于转换 IEnumerable 的扩展类,以及一个下面的测试类:

            /// <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();
              }
            }
            

            这是扩展类:

            /// <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);
              }
            }
            

            这是我的测试:

            [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]);
                }
              }
            }
            

            【讨论】:

              猜你喜欢
              • 2010-09-29
              • 2010-10-01
              • 2016-06-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-11-17
              • 1970-01-01
              • 2022-08-15
              相关资源
              最近更新 更多