Использование перечисления в качестве индекса массива в C#

Я хочу сделать то же самое, что и в этом вопросе, то есть:

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

...

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

однако я бы предпочел иметь что-то неотъемлемое, чем писать этот код, подверженный ошибкам. Есть ли встроенный модуль в С#, который делает именно это?


person Nefzen    schedule 11.06.2009    source источник
comment
Небольшой комментарий относительно вашего имени DaysOfTheWeek: стандарт C# говорит, что перечисления без флагов должны иметь имена в единственном числе, а перечисления в стиле флагов должны иметь имена во множественном числе, поэтому DayOfTheWeek будет лучше. msdn.microsoft.com/en-us/library/ms229040.aspx   -  person RenniePet    schedule 19.05.2014


Ответы (10)


Если значения ваших элементов перечисления непрерывны, метод массива работает очень хорошо. Однако в любом случае вы можете использовать Dictionary<DayOfTheWeek, string> (кстати, менее производительный).

person mmx    schedule 11.06.2009
comment
Это сильно повлияет на производительность? Почему? - person Spencer Ruport; 11.06.2009
comment
@Spencer: поиск в словаре намного медленнее, чем прямой индекс массива (или индекс списка). Если вы делаете это часто, это может оказать заметное влияние на производительность. - person Reed Copsey; 11.06.2009
comment
да, это решение, которое я выбрал для MouseButton, поскольку это перечисление флагов (также известное как 0001 справа, 0010 посередине, 0100 слева и т. д.). Тем не менее, довольно уродливо для такой простой вещи. - person Nefzen; 11.06.2009
comment
@Spencer: я не сказал значительного. Важно это или нет, зависит от вашего конкретного использования. Это медленнее? Да, без сомнения, словарь медленнее, чем прямой поиск в массиве. Это важно? Вы должны сравнить и посмотреть. - person mmx; 11.06.2009
comment
Решение со словарем предпочтительнее, если вы не можете доказать, что есть значительное влияние. Предварительная оптимизация для массива создает две проблемы: во-первых, она отказывается от безопасности типов перечислений, делая ее фактически ничем не отличающейся (но более надуманной) от констант, определенных в статическом классе. Во-вторых, вы в конечном итоге пишете больше кода, а не меньше, чтобы заставить его вести себя так, как вы хотите. - person Michael Meadows; 11.06.2009
comment
это не ответ на вопрос - person Paulo Neves; 16.06.2020
comment
Плохая идея, ужасное исполнение. - person Alexandre M; 13.05.2021

Начиная с C# 7.3 стало возможным использовать System.Enum как ограничение на параметры типа. Таким образом, неприятные хаки в некоторых других ответах больше не требуются.

Вот очень простой класс 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
person Ian Goldby    schedule 21.06.2018
comment
Ваш ответ работает хорошо. Он дает доступ для записи к элементам массива. Другой общий ответ (класс Caster от @Matthew Whited) дает только чтение, поэтому вы должны инициализировать массив экземплярами, после чего вы не можете изменить экземпляр (только писать в нем). MyTable[Seat.East] = новый игрок {Имя = Джо, ID = 1234}; Однако его Caster Deserializes лучше. У меня была проблема с исключением с десериализацией (NewtonSoft) с вашим решением. - person Guy; 16.02.2020

Вы можете создать класс или структуру, которая сделает всю работу за вас.


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]);
person Matthew Whited    schedule 11.06.2009
comment
+1, это, по крайней мере, заключает в себе боль от попытки впихнуть перечисление в то, для чего оно не предназначено (и не должно использоваться). - person Michael Meadows; 11.06.2009
comment
Ну, я бы согласился, что для сеттеров надо сделать что-то более вменяемое. Но я бы никогда не покрыл это чем-то, что не следует использовать. Другим вариантом может быть создание пользовательских структур только для чтения, которые используются аналогичным образом. - person Matthew Whited; 11.06.2009
comment
@ Мэтью Уайтд, я, вероятно, слишком остро реагирую, когда говорю, что его не следует использовать, но лично я никогда не видел и не могу представить вескую оправданную причину для создания перечисления только для того, чтобы оно служило индексатором в массиве. Я могу с уверенностью сказать, что в этом ограниченном случае вы теряете все преимущества перечисления и ничего не получаете взамен. - person Michael Meadows; 11.06.2009
comment
В качестве упражнения вы можете попытаться сделать свое решение универсальным. Я думаю, что это можно сделать, и это заставит его еще больше @$$. - person Michael Meadows; 11.06.2009
comment
Generics и Enums не очень хорошо сочетаются друг с другом. Но я согласен, что это было бы очень круто. Если вместо этого вы использовали класс для индекса, вы могли бы использовать либо абстрактную базу, либо интерфейс в качестве типа индексатора. Но у вас все равно будут проблемы с неявными преобразованиями, для которых вместо этого потребуется статический фабричный метод. - person Matthew Whited; 11.06.2009
comment
Да, вы правы... Вам нужно будет использовать ключ как перечисление во время выполнения. Это больше кода, но все еще может быть полезно. Однако неявное приведение по-прежнему должно работать... Я преобразовал ваш код в универсальный для развлечения, но на самом деле, со всем дополнительным кодом, он, вероятно, работает хуже, чем словарь. :( - person Michael Meadows; 12.06.2009
comment
Вы должны разместить код где-нибудь и связать его здесь. Я хотел бы увидеть ваше решение. - person Matthew Whited; 12.06.2009
comment
Я не знал, куда еще его поместить, поэтому я отредактировал его в вашем ответе. - person Michael Meadows; 12.06.2009
comment
Было бы намного лучше, если бы Enums и Generics играли лучше. Было бы интересно, чтобы дженерики хорошо работали с атрибутами, и если бы в текущей версии .Net существовали такие интерфейсы, как INumeric (я знаю, что их можно подделать с помощью DRL, но это не то же самое). - person Matthew Whited; 12.06.2009
comment
@MichaelMeadows, что, если у вас есть объекты солнечной системы (Солнце, Земля, Луна и т. д.), которые вы хотите использовать в качестве перечисления, когда вы жестко задаете их свойства (имя, массу, местоположение, скорость и т. д.)? Вместо доступа к массиву через 0, 1, 2 и т. д. было бы полезно получить доступ к ним через Солнце, Землю, Луну и т. д. Какое решение может быть более элегантным? Имейте в виду, что значения могут быть жестко закодированы, поскольку они по существу являются константами. - person Xonatron; 07.01.2018
comment
Ваш ответ обеспечивает доступ только для чтения к элементам в массиве, поэтому вы должны инициализировать массив так, как вы это делаете (с экземплярами). После этого вы не можете заменить экземпляр (только писать в него). Невозможно написать MyTable[Player.East] = new Player { Name = "Joe", ID = 1234 }; Однако в решении @IanGoldby у меня возникла проблема с исключением при десериализации (в то время как у вас все работает нормально). - person Guy; 16.02.2020

Компактная форма перечисления, используемая в качестве индекса и присваивающая любой тип словарю и строго типизированная. В этом случае возвращаются значения с плавающей запятой, но значения могут быть сложными экземплярами класса, имеющими свойства, методы и многое другое:

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];
person tofo    schedule 03.11.2014

Ну вот:

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

Если вам действительно нужна длина, просто возьмите .Length в результате :) Вы можете получить значения с помощью:

string[] message_array = Enum.GetValues(typeof(DaysOfTheWeek));
person van    schedule 11.06.2009

Если все, что вам нужно, это, по сути, карта, но вы не хотите нести накладные расходы на производительность, связанные с поиском по словарю, это может сработать:

    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.

person Zar Shardan    schedule 06.11.2014

Я понимаю, что это старый вопрос, но было несколько комментариев о том, что все решения до сих пор имеют проверки во время выполнения, чтобы убедиться, что тип данных является перечислением. Вот полное решение (с некоторыми примерами) решения с проверкой времени компиляции (а также некоторые комментарии и обсуждения от моих коллег-разработчиков)

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

РЕДАКТИРОВАТЬ: Если вы используете С# 7.3 или более позднюю версию, ПОЖАЛУЙСТА, не используйте это уродливое решение. См. ответ Яна Голдби от 2018 года.

person David I. McIntosh    schedule 11.05.2017

Вы всегда можете сделать дополнительное сопоставление, чтобы получить индекс массива значения перечисления согласованным и определенным образом:

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

Будьте как можно более конкретными. Однажды кто-то изменит ваше перечисление, и код выйдет из строя, потому что значение перечисления было (неправильно) использовано в качестве индекса массива.

person VVS    schedule 11.06.2009
comment
в этом случае имеет смысл просто указать значения в самом определении Enum. - person van; 11.06.2009
comment
@van, вы правы насчет этого случая, но в утверждении @David Humpohl о том, что код может в конечном итоге дать сбой, есть некоторые основания. В случае DaysOfWeek шансы невелики, но перечисления, основанные на бизнес-значениях, могут измениться, что приведет к смещению базовых значений. - person Michael Meadows; 11.06.2009

Для дальнейшего использования вышеуказанную проблему можно резюмировать следующим образом:

Я пришел из 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#.

person Graham Harris    schedule 05.09.2016

Это был очень хороший ответ от @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]);
    }
  }
}
person John Stewien    schedule 13.11.2020