2009-06-11 10 views
23

quiero hacer lo mismo que en this question, es decir:El uso de una enumeración como un índice de matriz en C#

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

... 

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

, yo preferiría tener algo integral a lo que, en lugar de escribir el código propenso a errores . ¿Hay un módulo integrado en C# que hace esto?

+3

comentario Tiny sobre su nombre, "díasDeLaSemana": El C# norma dice que las enumeraciones de estilo no de banderas deben tener nombres singulares y las enumeraciones de estilo de banderas deben tener nombres en plural, por lo que "DayOfTheWeek" sería mejor. http://msdn.microsoft.com/en-us/library/ms229040.aspx – RenniePet

Respuesta

15

Si los valores de los elementos de su enumeración son contigiosos, el método de matriz funciona bastante bien. Sin embargo, en cualquier caso, puede usar Dictionary<DayOfTheWeek, string> (que es menos eficiente, por cierto).

+0

Eso tendría un impacto significativo en el rendimiento? ¿Cómo? –

+1

@Spencer: las búsquedas de diccionario son mucho más lentas que un índice de matriz directa (o índice de lista). Si estás haciendo esto mucho, podría tener un impacto notable en el rendimiento. –

+0

sí, esa es la solución que elegí para MouseButton, ya que es una enumeración de bandera (también conocida como 0001 derecha, 0010 central, 0100 izquierda, etc.). Aún así, bastante feo para algo tan simple. – Nefzen

7

Se podría hacer una clase o estructura que podría hacer el trabajo para usted


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

EDITAR

Por falta de un mejor lugar para poner esto, estoy publicando un genérico versión de la clase Caster a continuación. Desafortunadamente, confía en los controles de tiempo de ejecución para aplicar TKey como una enumeración.

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

+1, esto, al menos, encapsula el dolor de tratar de calzar una enumeración para hacer algo para lo que no estaba diseñado (y no debería usarse). –

+0

Bueno, estoy de acuerdo en que debería hacerse algo más sensato para los incubadores. Pero nunca lo cubriría como algo que "no debería usarse". Otra opción sería crear estructuras de solo lectura personalizadas que se utilizan de manera similar. –

+0

@Matthew Whited, probablemente estoy reaccionando demasiado cuando digo "no debería usarse", pero personalmente nunca he visto, y no puedo imaginar, una buena razón para crear una enumeración solo para servir como un indexador en una matriz. Puedo decir con confianza que, en este caso limitado, pierdes todos los beneficios de una enumeración y no obtienes nada a cambio. –

4

Aquí van:

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

Si realmente necesita la longitud, luego tomar la .length en el resultado :) Puede obtener valores con:

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

Siempre puede hacer un mapeo extra para obtener un índice de matriz de un valor enum de una manera consistente y definida:

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

Sea lo más específico que pueda. Algún día alguien modificará tu enumeración y el código fallará porque el valor de la enumeración se usó (mal) como un índice de matriz.

+1

en este caso sería ma ke sense solo para especificar valores en la definición de Enum. – van

+0

@van, tiene razón en este caso, pero hay algo de mérito en la afirmación de @David Humpohl de que el código podría fallar. En el caso de DaysOfWeek, las posibilidades son bajas, pero las enumeraciones basadas en valores comerciales podrían cambiar, lo que provocaría un cambio en los valores subyacentes. –

2

Forma compacta de enum usada como índice y asignando cualquier tipo a un Dictionary y fuertemente tipada. En este caso se devuelven valores de coma flotante, pero los valores pueden ser instancias de clases complejas que tienen propiedades y métodos y más:

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

Si todo lo que necesita es esencialmente un mapa, pero no quieren incurrir en el gasto de recursos asociado a búsquedas de diccionario , esto podría funcionar:

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

Así que en su caso podría derivar:

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

Uso:

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

Obviamente, esta aplicación está respaldada por una matriz, por lo que las enumeraciones no contiguos como este:

enum 
{ 
    Ok = 1, 
    NotOk = 1000000 
} 

daría lugar a un uso excesivo de memoria.

Si necesita el máximo rendimiento posible, puede hacerlo menos genérico y perder todo el código genérico de manejo enum que tuve que usar para que compilara y funcionara. Sin embargo, no hice un benchmark, así que tal vez no sea gran cosa.

Almacenar en caché las claves de propiedad estática también puede ayudar.

0

Para futura referencia el problema anterior se puede resumir de la siguiente manera:

Vengo de Delphi donde se puede definir una matriz de la siguiente manera:

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

    TDaysOfTheWeekStrings = array[TDaysOfTheWeek); 

A continuación, se puede recorrer la matriz mediante Min y max:

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

Aunque este es un buen ejemplo artificioso, cuando se trata de posiciones de la red más adelante en el código que puede simplemente escribir DaysOfTheWeekStrings[TDaysOfTheWeek.Monday] . Esto tiene la ventaja del hecho de que si el TDaysOfTheWeek aumenta en tamaño, entonces no tengo que recordar el nuevo tamaño de la matriz, etc. ..... Sin embargo, volvemos al mundo de C#. Encontré este ejemplo C# Enum Array Example.

0

Me doy cuenta de que esta es una pregunta antigua, pero ha habido una serie de comentarios sobre el hecho de que todas las soluciones hasta ahora tienen comprobaciones de tiempo de ejecución para garantizar que el tipo de datos sea una enumeración. Aquí es una solución completa (con algunos ejemplos) de una solución con controles de tiempo de compilación (así como algunos comentarios de mis colegas desarrolladores)

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