在 C# 中使用枚举作为数组索引

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/981776/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 04:49:37  来源:igfitidea点击:

Using an enum as an array index in C#

c#indexingenums

提问by Nefzen

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

...

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

however, I would rather have something integral to so, rather than write this error prone code. Is there a built in module in C# that does just this?

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

回答by Mehrdad Afshari

If the values of your enum items are contigious, the array method works pretty well. However, in any case, you could use Dictionary<DayOfTheWeek, string>(which is less performant, by the way).

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

回答by Matthew Whited

You could make a class or struct that could do the work for you

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



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

EDIT

编辑

For lack of a better place to put this, I am posting a generic version of the Caster class below. Unfortunately, it relies on runtime checks to enforce TKey as an enum.

由于没有更好的地方来放置它,我在下面发布了 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]);

回答by van

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:

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

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

回答by VVS

You can always do some extra mapping to get an array index of an enum value in a consistent and defined way:

您始终可以进行一些额外的映射,以一致且定义的方式获取枚举值的数组索引:

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

Be as specific as you can. One day someone will modify your enum and the code will fail because the enum's value was (mis)used as an array index.

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

回答by tofo

Compact form of enum used as index and assigning whatever type to a Dictionary and strongly typed. In this case float values are returned but values could be complex Class instances having properties and methods and more:

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

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

回答by Zar Shardan

If all you need is essentially a map, but don't want to incur performance overhead associated with dictionary lookups, this might work:

如果您只需要一张地图,但又不想招致与字典查找相关的性能开销,那么这可能有效:

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

So in your case you could derive:

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

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

Usage:

用法:

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

Obviously this implementation is backed by an array, so non-contiguous enums like this:

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

enum
{
  Ok = 1,
  NotOk = 1000000
}

would result in excessive memory usage.

会导致内存使用过多。

If you require maximum possible performance you might want to make it less generic and loose all generic enum handling code I had to use to get it to compile and work. I didn't benchmark this though, so maybe it's no big deal.

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

Caching the Keys static property might also help.

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

回答by Graham Harris

For future reference the above problem can be summarized as follows:

上述问题可以总结如下,以供日后参考:

I come from Delphi where you can define an array as follows:

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

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

  TDaysOfTheWeekStrings = array[TDaysOfTheWeek];

Then you can iterate through the array using Min and Max:

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

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

Though this is quite a contrived example, when you are dealing with array positions later in the code I can just type DaysOfTheWeekStrings[TDaysOfTheWeek.Monday]. This has the advantage of the fact that I should the TDaysOfTheWeekincrease in size then I do not have to remember the new size of the array etc..... However back to the C# world. I have found this example C# Enum Array Example.

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

回答by David I. McIntosh

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.

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

回答by Ian Goldby

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

从 C# 7.3 开始,可以将其System.Enum用作类型参数的约束。因此,不再需要其他一些答案中令人讨厌的黑客攻击。

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

这是一个非常简单的ArrayByEum类,它完全满足了问题的要求。

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.

请注意,如果枚举值不连续,它将浪费空间,并且不会处理对于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();
  }
}

Usage:

用法:

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

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