Вопрос по indexing, enums, c# – Использование перечисления в качестве индекса массива в C #

25

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

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 RenniePet

Ваш Ответ

9   ответов
0

на следующим образом:

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

2

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

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];
0

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

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

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

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

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
2

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

    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
}

приведет к чрезмерному использованию памяти.

Если вам требуется максимально возможная производительность, вы можете сделать ее менее универсальной и потерять весь общий код обработки enum, который я должен был использовать, чтобы заставить его компилировать и работать. Я не тестировал это, хотя, возможно, это не имеет большого значения.

Кэширование статического свойства ключей также может помочь.

7

которая могла бы сделать работу за вас

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, это, по крайней мере, заключает в себе боль от попытки заставить enum сделать что-то, для чего он не был предназначен (и не должен использоваться). Michael Meadows
Generics и Enums не очень хорошо играют вместе. Но я согласен, что это было бы очень круто. Если вместо этого вы использовали класс для индекса, вы можете использовать либо абстрактную базу, либо интерфейс в качестве типа индексатора. Но у вас все равно будут проблемы с неявными преобразованиями, для которых вместо этого потребуется статический фабричный метод. Matthew Whited
В качестве упражнения вы можете попытаться сделать свое решение общим. Я думаю, что это может быть сделано, и заставило бы это пнуть еще больше @ $. Michael Meadows
Что ж, я бы согласился, что для сеттеров нужно сделать что-то более вменяемое. Но я бы никогда не назвал это чем-то, что «не должно использоваться». Другой вариант - создание пользовательских структур только для чтения, которые используются аналогичным образом. Matthew Whited
@ Matthew Whited, я, вероятно, чрезмерно реагирую, когда говорю «не следует использовать», но лично я никогда не видел и не могу себе представить вескую обоснованную причину создания перечисления только для использования в качестве индексатора в массив. Я могу с уверенностью сказать, что в этом ограниченном случае вы теряете все преимущества перечисления и ничего не получаете взамен. Michael Meadows
17

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

Это будет иметь значительное влияние на производительность? Как придешь? Spencer Ruport
@Spencer: поиск в словаре намного медленнее, чем прямой индекс массива (или индекс списка). Если вы делаете это много, это может оказать заметное влияние на производительность. Reed Copsey
@ Спенсер: я не сказал "значительным". Важно это или нет, зависит от вашего конкретного использования. Это медленнее? Да, без сомнения, Словарь работает медленнее, чем прямой поиск в массиве. Это важно? Вы должны сравнить и посмотреть. Mehrdad Afshari
да, это решение, которое я выбрал для MouseButton, так как это перечисление флага (также 0001 справа, 0010 посередине, 0100 слева и т. д.). Тем не менее, довольно некрасиво для такой простой вещи. Nefzen
Словарное решение предпочтительнее, если вы не можете доказать, что оно оказывает значительное влияние. Предварительная оптимизация для массива создает две проблемы: во-первых, он отказывается от безопасности типов перечислений, делая его практически не отличным (но более надуманным), чем константы, определенные в статическом классе. Во-вторых, вы в конечном итоге пишете больше кода, чем меньше, чтобы заставить его вести себя так, как вы хотите. Michael Meadows
4

Ну вот:

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

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

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

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

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

Похожие вопросы