2009-06-11 17 views
23

voglio fare lo stesso come in this question, cioè:Utilizzando un enum come indice array in C#

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

... 

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

tuttavia, avrei preferito qualcosa integrante così, invece di scrivere questo codice inclini errore . C'è un modulo integrato in C# che fa proprio questo?

+3

piccolo commento per quanto riguarda il tuo nome "DaysOfTheWeek": Il C# di serie dice che le enumerazioni non in stile flags dovrebbero avere nomi singolari e le enumerazioni in stile flags dovrebbero avere nomi plurali, quindi "DayOfTheWeek" sarebbe meglio. http://msdn.microsoft.com/en-us/library/ms229040.aspx – RenniePet

risposta

15

Se i valori degli elementi enumerati sono contagiosi, il metodo array funziona piuttosto bene. Tuttavia, in ogni caso, è possibile utilizzare Dictionary<DayOfTheWeek, string> (che è meno performante, comunque).

+0

Ciò avrebbe un impatto significativo sulle prestazioni? Come mai? –

+1

@Spencer: le ricerche del dizionario sono molto più lente di un indice di array diretto (o indice di elenco). Se lo fai molto, potrebbe avere un impatto notevole sul perf. –

+0

sì, questa è la soluzione che ho scelto per MouseButton, dato che si tratta di un flag enum (alias 0001 right, 0010 middle, 0100 left ecc.). Ancora, piuttosto brutto per una cosa così semplice. – Nefzen

7

Si potrebbe fare una classe o struct che potrebbe fare il lavoro per voi


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

Per mancanza di un posto migliore per mettere questo, sto postando un generico versione della classe Caster di seguito. Sfortunatamente, si basa sui controlli di runtime per far rispettare TKey come enum.

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, questo, almeno, incapsula il dolore di provare a calzare un enum in qualcosa che non è stato progettato per (e non dovrebbe essere usato per fare). –

+0

Beh, sarei d'accordo che qualcosa di più sano dovrebbe essere fatto per i setter. Ma non lo coprirò mai come qualcosa che "non dovrebbe essere usato". Un'altra opzione potrebbe essere quella di creare strutture readonly personalizzate utilizzate in modo simile. –

+0

@ Matthew Whited, probabilmente reagisco in modo esagerato quando dico "non dovrebbe essere usato", ma personalmente non ho mai visto, e non posso immaginare, un buon motivo giustificabile per creare un enum solo per servire da indicizzatore un array. Posso dire con sicurezza che, in questo caso limitato, perdi tutti i benefici di un enum e non ottieni nulla in cambio. –

4

Qui si va:

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

Se si ha realmente bisogno la lunghezza, poi basta prendere il .Length sul risultato :) È possibile ottenere i valori di:

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

È sempre possibile eseguire alcune mappature aggiuntive per ottenere un indice di array di un valore enum in modo coerente e definito:

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

Sii il più specifico possibile. Un giorno qualcuno modificherà l'enumerazione e il codice fallirà perché il valore dell'enumerazione è stato (errato) utilizzato come indice dell'array.

+1

in questo caso sarebbe ma ke ha senso solo per specificare i valori nella definizione Enum stessa. – van

+0

@van, hai ragione su questo caso, ma c'è qualche merito per l'affermazione di @David Humpohl che il codice potrebbe fallire. Nel caso di DaysOfWeek, le probabilità sono basse, ma le enumerazioni basate sui valori aziendali potrebbero cambiare causando lo spostamento dei valori sottostanti. –

2

Forma compatta di enumerazione utilizzata come indice e assegnazione di qualsiasi tipo a un dizionario e fortemente digitato. In questo caso i valori float vengono restituiti, ma i valori potrebbero essere casi di classe complesso di proprietà e metodi e di più:

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

Se tutto ciò che serve è essenzialmente una mappa, ma non vogliono incorrere in sovraccarico delle prestazioni associato con le ricerche del dizionario , questo potrebbe funzionare:

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

Quindi nel tuo caso si potrebbero derivare:

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

Ovviamente questa implementazione è supportato da un array, enum quindi non contigui come questo:

enum 
{ 
    Ok = 1, 
    NotOk = 1000000 
} 

otterrebbe come risultato dell'utilizzo eccessivo di memoria.

Se si richiedono le massime prestazioni possibili, è possibile renderlo meno generico e perdere tutto il codice di gestione enum generico che dovevo utilizzare per farlo compilare e lavorare. Non ho fatto un benchmark, quindi forse non è un grosso problema.

Anche la proprietà statica di Caching the Keys può essere d'aiuto.

0

Per riferimento futuro il problema di cui sopra può essere sintetizzata come segue:

Vengo da Delfi, dove è possibile definire un array come segue:

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

    TDaysOfTheWeekStrings = array[TDaysOfTheWeek); 

Quindi è possibile scorrere l'array usando Min e Max:

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

Anche se questo è piuttosto un esempio forzato, quando hai a che fare con le posizioni di matrice più avanti nel codice posso solo digitare DaysOfTheWeekStrings[TDaysOfTheWeek.Monday] . Questo ha il vantaggio del fatto che dovrei aumentare la dimensione dello TDaysOfTheWeek, quindi non devo ricordare la nuova dimensione dell'array ecc ..... Comunque torniamo al mondo C#. Ho trovato questo esempio C# Enum Array Example.

0

Mi rendo conto che questa è una vecchia domanda, ma ci sono stati un certo numero di commenti sul fatto che tutte le soluzioni finora hanno controlli di runtime per garantire che il tipo di dati sia un enume. Qui è una soluzione completa (con alcuni esempi) di una soluzione con controlli in fase di compilazione (come pure alcuni commenti e discussioni da parte dei miei colleghi sviluppatori)

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