Crear un diccionario constante en C #

128

¿Cuál es la forma más eficiente de crear una asignación constante (nunca cambia en tiempo de ejecución) de strings a ints?

Intenté usar un diccionario const , pero eso no funcionó.

Podría implementar un contenedor inmutable con la semántica adecuada, pero eso todavía no parece del todo correcto.


Para aquellos que han preguntado, estoy implementando IDataErrorInfo en una clase generada y estoy buscando una manera de hacer que el columnName busque en mi matriz de descriptores.

No sabía (error tipográfico al probar! D'oh!) Que el interruptor acepta cadenas, así que eso es lo que voy a usar. ¡Gracias!

David Schmitt
fuente
1
hay una solución aquí: stackoverflow.com/questions/10066708/…

Respuestas:

180

Crear un diccionario constante realmente generado en tiempo de compilación en C # no es realmente una tarea sencilla. En realidad, ninguna de las respuestas aquí realmente logra eso.

Sin embargo, hay una solución que cumple con sus requisitos, aunque no necesariamente una buena; recuerde que de acuerdo con la especificación de C #, las tablas de mayúsculas y minúsculas se compilan en tablas hash jump constantes. Es decir, son diccionarios constantes, no una serie de declaraciones if-else. Entonces considere una declaración de cambio de caso como esta:

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

Esto es exactamente lo que quieres. Y sí, lo sé, es feo.

Tamas Czinege
fuente
9
De todos modos, es código generado, tiene buenas características de rendimiento, se puede calcular en tiempo de compilación y también tiene otras buenas propiedades para mi caso de uso. Lo haré así!
David Schmitt, el
44
solo tenga cuidado de que devolver tipos sin valor, incluso así, rompa la inmutabilidad de sus clases.
Tom Anderson
35
switch-case es increíble, hasta que necesite "predecir" a través de los valores.
Alex
66
Carece de muchas funciones agradables que tienen los diccionarios.
Zinan Xing
1
@TomAnderson: la construcción se comportará como inmutable si los elementos devueltos son tipos de valor (mutables o no), o si son objetos de clase inmutables.
supercat
37

Hay muy pocas colecciones inmutables en el marco actual. Puedo pensar en una opción relativamente indolora en .NET 3.5:

Uso Enumerable.ToLookup(): la Lookup<,>clase es inmutable (pero tiene varios valores en la HR); puedes hacer esto de una manera Dictionary<,>bastante fácil:

    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();
Marc Gravell
fuente
15
enum Constants
{
    Abc = 1,
    Def = 2,
    Ghi = 3
}

...

int i = (int)Enum.Parse(typeof(Constants), "Def");
Richard Poole
fuente
2
¡Idea interesante! Me pregunto qué tan bien funciona la llamada Parse (). Me temo que solo un perfilador puede responder eso.
David Schmitt
10

Esto es lo más parecido a un "Diccionario CONST":

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

El compilador será lo suficientemente inteligente como para construir el código lo más limpio posible.

Timothy Khouri
fuente
8

Si usara 4.5+ Framework, usaría ReadOnlyDictionary (también ReadOnly Collection para listas) para hacer asignaciones / constantes de solo lectura. Se implementa de la siguiente manera.

static class SomeClass
{
    static readonly ReadOnlyDictionary<string,int> SOME_MAPPING 
        = new ReadOnlyDictionary<string,int>(
            new Dictionary<string,int>()
            {
                { "One", 1 },
                { "Two", 2 }
            }
        )
}        
Kram
fuente
private static readonly Dictionary<string, string> _yourDictionaryName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { { "One",1 }, { "Two",2 }, { "Three",3 }, };Así es como lo hice.
Sanket Sonavane
@SanketSonavane a readonly Dictionary<TKey, TValue>no es equivalente a a ReadOnlyDictionary<TKey, TValue>. De hecho, su forma de "solo lectura" es bastante opuesta entre sí. Ver: dotnetfiddle.net/v0l4aA .
Broots Waymb
2

¿Por qué no usar espacios de nombres o clases para anidar sus valores? Puede ser imperfecto, pero está muy limpio.

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

Ahora puede acceder a .ParentClass.FooDictionary.Key1, etc.

Joshua
fuente
Porque esto no implementa la interfaz IDataErrorInfo.
David Schmitt
1

No parece haber una interfaz inmutable estándar para los diccionarios, por lo que, desafortunadamente, la creación de un contenedor parece ser la única opción razonable.

Editar : Marc Gravell encontró el ILookup que me perdí, que le permitirá al menos evitar crear un nuevo contenedor, aunque aún necesita transformar el Diccionario con .ToLookup ().

Si esta es una necesidad restringida a un escenario específico, es mejor que tenga una interfaz más orientada a la lógica empresarial:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}
Lijadora
fuente
1

No estoy seguro de por qué nadie mencionó esto, pero en C # para cosas que no puedo asignar const, uso propiedades estáticas de solo lectura.

Ejemplo:

public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
        {
            { "Reference1", Array1 },
            { "Reference2", Array2 },
            { "Reference3", Array3 },
            { "Reference4", Array4 },
            { "Reference5", Array5 }
        };
Solimán
fuente
porque el contenido del diccionario todavía es mutable y se asigna en tiempo de ejecución.
David Schmitt
0

Solo otra idea, ya que estoy vinculado a un cuadro combinado 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;

Para obtener el valor int del nombre para mostrar de :

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

uso:

var z = (int)EnumHelper<DateRange>.GetValueFromName("Last 7 Days");
Gina Marano
fuente
-2

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

fuente
3
Porque esto es bastante costoso en comparación con las alternativas disponibles en las condiciones especificadas.
David Schmitt
También porque esto ni siquiera compila
AustinWBryan
@AustinWBryan sí se compila
derHugo