Criando um dicionário constante em c #

128

Qual é a maneira mais eficiente de criar um mapeamento constante (nunca muda em tempo de execução) de strings para ints?

Eu tentei usar um dicionário const , mas isso não deu certo.

Eu poderia implementar um invólucro imutável com semântica apropriada, mas isso ainda não parece totalmente correto.


Para aqueles que perguntaram, estou implementando IDataErrorInfo em uma classe gerada e estou procurando uma maneira de fazer a pesquisa columnName na minha matriz de descritores.

Eu não sabia (erro de digitação ao testar! D'oh!) Que o switch aceita strings, então é isso que eu vou usar. Obrigado!

David Schmitt
fonte
1
existe uma solução aqui: stackoverflow.com/questions/10066708/…

Respostas:

180

Criar um dicionário constante gerado em tempo de compilação verdadeiramente em C # não é realmente uma tarefa simples. Na verdade, nenhuma das respostas aqui realmente consegue isso.

Existe uma solução que atende aos seus requisitos, embora não seja necessariamente uma boa; lembre-se de que, de acordo com a especificação C #, as tabelas de casos de comutação são compiladas em tabelas de salto de hash constantes. Ou seja, são dicionários constantes, não uma série de instruções if-else. Portanto, considere uma declaração de caso de troca como esta:

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

É exatamente isso que você deseja. E sim, eu sei, é feio.

Tamas Czinege
fonte
9
De qualquer forma, ele gerou código, possui boas características de desempenho, pode ser calculado em tempo de compilação e também possui outras propriedades interessantes para o meu caso de uso. Eu vou fazer assim!
David Schmitt
4
apenas tome cuidado para que retornar tipos sem valor, mesmo assim, quebre a imutabilidade de suas classes.
Tom Anderson
35
alternar caso é incrível, até que você precise "foreach" através dos valores.
28411 Alex
6
Falta muitas funções interessantes que os Dicionários têm.
Zinan Xing
1
@ TomAnderson: A construção se comportará como imutável se os itens retornados forem do tipo valor (mutáveis ​​ou não) ou se forem objetos de classe imutáveis.
Supercat
37

Existem poucas coleções imutáveis ​​na estrutura atual. Posso pensar em uma opção relativamente simples no .NET 3.5:

Uso Enumerable.ToLookup()- a Lookup<,>classe é imutável (mas com vários valores no rhs); você pode fazer isso Dictionary<,>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();
Marc Gravell
fonte
15
enum Constants
{
    Abc = 1,
    Def = 2,
    Ghi = 3
}

...

int i = (int)Enum.Parse(typeof(Constants), "Def");
Richard Poole
fonte
2
Idéia interessante! Eu estou querendo saber o quão boa a chamada Parse () executa. Receio que apenas um criador de perfil possa responder a essa pergunta.
David Schmitt
10

Essa é a coisa mais próxima que você pode obter de um "Dicionário CONST":

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

O compilador será inteligente o suficiente para criar o código o mais limpo possível.

Timothy Khouri
fonte
8

Se estiver usando o 4.5+ Framework, eu usaria o ReadOnlyDictionary (também ReadOnly Collection para listas) para fazer mapeamentos / constantes somente leitura. É implementado da seguinte maneira.

static class SomeClass
{
    static readonly ReadOnlyDictionary<string,int> SOME_MAPPING 
        = new ReadOnlyDictionary<string,int>(
            new Dictionary<string,int>()
            {
                { "One", 1 },
                { "Two", 2 }
            }
        )
}        
Kram
fonte
private static readonly Dictionary<string, string> _yourDictionaryName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { { "One",1 }, { "Two",2 }, { "Three",3 }, };Foi assim que eu fiz.
Sanket Sonavane
@SanketSonavane a readonly Dictionary<TKey, TValue>não é equivalente a a ReadOnlyDictionary<TKey, TValue>. De fato, a maneira como eles são "somente leitura" é praticamente o oposto um do outro. Veja: dotnetfiddle.net/v0l4aA .
Broots Waymb
2

Por que não usar namespaces ou classes para aninhar seus valores? Pode ser imperfeito, mas é muito limpo.

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

Agora você pode acessar .ParentClass.FooDictionary.Key1, etc.

Joshua
fonte
Porque isso não implementa a interface IDataErrorInfo.
21718 David Schmitt
1

Não parece haver nenhuma interface imutável padrão para dicionários; portanto, criar um wrapper parece ser a única opção razoável, infelizmente.

Edit : Marc Gravell encontrou o ILookup que eu perdi - que permitirá pelo menos evitar a criação de um novo wrapper, embora você ainda precise transformar o Dicionário com .ToLookup ().

Se for uma necessidade restrita a um cenário específico, você poderá se beneficiar com uma interface mais orientada à lógica de negócios:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}
Sander
fonte
1

Não sei por que ninguém mencionou isso, mas em C # para coisas que não posso atribuir const, uso propriedades estáticas somente leitura.

Exemplo:

public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
        {
            { "Reference1", Array1 },
            { "Reference2", Array2 },
            { "Reference3", Array3 },
            { "Reference4", Array4 },
            { "Reference5", Array5 }
        };
Suleman
fonte
porque o conteúdo do dicionário ainda é mutável e é alocado em tempo de execução.
David Schmitt
0

Apenas outra idéia, pois estou vinculando uma caixa de combinação 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 obter o valor int do nome de exibição 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
fonte
-2

Por que não:

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

fonte
3
Porque isso é bastante caro comparado às alternativas disponíveis nas condições especificadas.
David Schmitt
Também porque este nem sequer compilar
AustinWBryan
@AustinWBryan sim, compila
derHugo 17/08