Creazione di un dizionario costante in C #


128

Qual è il modo più efficiente per creare una mappatura costante (mai in fase di esecuzione) da strings a ints?

Ho provato ad usare un dizionario const , ma non ha funzionato.

Potrei implementare un wrapper immutabile con semantica appropriata, ma ciò non sembra ancora del tutto corretto.


Per coloro che hanno chiesto, sto implementando IDataErrorInfo in una classe generata e sto cercando un modo per fare in modo che la ricerca columnName sia nel mio array di descrittori.

Non ero a conoscenza (errore di battitura durante il test! D'oh!) Che switch accetta le stringhe, quindi è quello che userò. Grazie!


Risposte:


180

La creazione di un dizionario costante generato in fase di compilazione in C # non è un compito semplice. In realtà, nessuna delle risposte qui lo raggiunge davvero.

Esiste però una soluzione che soddisfa le tue esigenze, sebbene non necessariamente una buona soluzione; ricorda che secondo la specifica C #, le tabelle di switch-case vengono compilate in tabelle di hash jump costanti. Cioè, sono dizionari costanti, non una serie di istruzioni if-else. Quindi considera un'istruzione switch-case come questa:

switch (myString)
{
   case "cat": return 0;
   case "dog": return 1;
   case "elephant": return 3;
}

Questo è esattamente quello che vuoi. E sì, lo so, è brutto.


9
È comunque generato codice, ha buone caratteristiche prestazionali, può essere calcolato in fase di compilazione e ha anche altre belle proprietà per il mio caso d'uso. Lo farò così!
David Schmitt,

4
fai solo attenzione a restituire tipi senza valore, anche in questo modo, rompendo l'immutabilità delle tue classi.
Tom Anderson,

35
switch-case è fantastico, fino a quando non è necessario "foreach" attraverso i valori.
Alex,

6
Manca molte funzioni interessanti che hanno i dizionari.
Zinan Xing,

1
@TomAnderson: il costrutto si comporterà immutabile se gli elementi restituiti sono tipi di valore (mutabili o meno) o se sono oggetti di classe immutabili.
supercat

37

Ci sono preziose poche collezioni immutabili nel quadro attuale. Posso pensare a un'opzione relativamente indolore in .NET 3.5:

Usa Enumerable.ToLookup(): la Lookup<,>classe è immutabile (ma multivalore su rhs); puoi farlo da Dictionary<,>abbastanza facilmente:

    Dictionary<string, int> ids = new Dictionary<string, int> {
      {"abc",1}, {"def",2}, {"ghi",3}
    };
    ILookup<string, int> lookup = ids.ToLookup(x => x.Key, x => x.Value);
    int i = lookup["def"].Single();

15
enum Constants
{
    Abc = 1,
    Def = 2,
    Ghi = 3
}

...

int i = (int)Enum.Parse(typeof(Constants), "Def");

2
Idea interessante! Mi chiedo quanto sia buona la chiamata Parse (). Temo che solo un profiler possa rispondere a quello.
David Schmitt,

10

Questa è la cosa più vicina a un "Dizionario COST":

public static int GetValueByName(string name)
{
    switch (name)
    {
        case "bob": return 1;
        case "billy": return 2;
        default: return -1;
    }
}

Il compilatore sarà abbastanza intelligente da creare il codice il più pulito possibile.


8

Se utilizzassi 4.5+ Framework userei ReadOnlyDictionary (anche ReadOnly Collection per gli elenchi) per eseguire mappature / costanti di sola lettura. È implementato nel modo seguente.

static class SomeClass
{
    static readonly ReadOnlyDictionary<string,int> SOME_MAPPING 
        = new ReadOnlyDictionary<string,int>(
            new Dictionary<string,int>()
            {
                { "One", 1 },
                { "Two", 2 }
            }
        )
}        

private static readonly Dictionary<string, string> _yourDictionaryName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { { "One",1 }, { "Two",2 }, { "Three",3 }, };È così che l'ho fatto.
Sanket Sonavane,

@SanketSonavane a readonly Dictionary<TKey, TValue>non è equivalente a a ReadOnlyDictionary<TKey, TValue>. In effetti, a modo loro sono "di sola lettura" è praticamente opposto l'uno all'altro. Vedi: dotnetfiddle.net/v0l4aA .
Broots Waymb

2

Perché non usare spazi dei nomi o classi per nidificare i tuoi valori? Può essere imperfetto, ma è molto pulito.

public static class ParentClass
{
    // here is the "dictionary" class
    public static class FooDictionary
    {
        public const string Key1 = "somevalue";
        public const string Foobar = "fubar";
    }
}

Ora puoi accedere a .ParentClass.FooDictionary.Key1, ecc.


Perché questo non implementa l'interfaccia IDataErrorInfo.
David Schmitt,

1

Non sembra esserci alcuna interfaccia immutabile standard per i dizionari, quindi la creazione di un wrapper sembra purtroppo l'unica opzione ragionevole.

Modifica : Marc Gravell ha trovato ILookup che mi mancava, che ti permetterà almeno di evitare di creare un nuovo wrapper, anche se devi ancora trasformare il Dizionario con .ToLookup ().

Se questa è una necessità limitata a uno scenario specifico, potresti stare meglio con un'interfaccia più orientata alla logica aziendale:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}

1

Non sono sicuro del motivo per cui nessuno ha menzionato questo, ma in C # per cose che non posso assegnare const, uso proprietà di sola lettura statiche.

Esempio:

public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
        {
            { "Reference1", Array1 },
            { "Reference2", Array2 },
            { "Reference3", Array3 },
            { "Reference4", Array4 },
            { "Reference5", Array5 }
        };

perché il contenuto del dizionario è ancora modificabile e viene allocato in fase di esecuzione.
David Schmitt,

0

Solo un'altra idea da quando sono vincolante per una casella combinata winforms:

public enum DateRange {
    [Display(Name = "None")]
    None = 0,
    [Display(Name = "Today")]
    Today = 1,
    [Display(Name = "Tomorrow")]
    Tomorrow = 2,
    [Display(Name = "Yesterday")]
    Yesterday = 3,
    [Display(Name = "Last 7 Days")]
    LastSeven = 4,
    [Display(Name = "Custom")]
    Custom = 99
    };

int something = (int)DateRange.None;

Per ottenere il valore int dal nome visualizzato da :

public static class EnumHelper<T>
{
    public static T GetValueFromName(string name)
    {
        var type = typeof(T);
        if (!type.IsEnum) throw new InvalidOperationException();

        foreach (var field in type.GetFields())
        {
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(DisplayAttribute)) as DisplayAttribute;
            if (attribute != null)
            {
                if (attribute.Name == name)
                {
                    return (T)field.GetValue(null);
                }
            }
            else
            {
                if (field.Name == name)
                    return (T)field.GetValue(null);
            }
        }

        throw new ArgumentOutOfRangeException("name");
    }
}

utilizzo:

var z = (int)EnumHelper<DateRange>.GetValueFromName("Last 7 Days");

-2

Perchè no:

public class MyClass
{
    private Dictionary<string, int> _myCollection = new Dictionary<string, int>() { { "A", 1 }, { "B", 2 }, { "C", 3 } };

    public IEnumerable<KeyValuePair<string,int>> MyCollection
    {
        get { return _myCollection.AsEnumerable<KeyValuePair<string, int>>(); }
    }
}

3
Perché questo è piuttosto costoso rispetto alle alternative disponibili nelle condizioni specificate.
David Schmitt,

Anche perché questo non si compila nemmeno
AustinWBryan

@AustinWBryan sì, si compila
derHugo il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.