Tenho alguns objetos em List, digamos, List<MyClass>
e MyClass tem várias propriedades. Eu gostaria de criar um índice da lista com base em 3 propriedades de MyClass. Neste caso, 2 das propriedades são int's e uma propriedade é uma data e hora.
Basicamente, gostaria de ser capaz de fazer algo como:
Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];
Às vezes, crio vários dicionários em uma lista para indexar diferentes propriedades das classes que ela contém. Não tenho certeza da melhor forma de lidar com chaves compostas. Considerei fazer uma soma de verificação dos três valores, mas isso corre o risco de colisões.
c#
dictionary
AaronLS
fonte
fonte
Respostas:
Você deve usar tuplas. Eles são equivalentes a uma classe CompositeKey, mas Equals () e GetHashCode () já estão implementados para você.
Ou usando System.Linq
A menos que você precise personalizar o cálculo do hash, é mais simples usar tuplas.
Se houver muitas propriedades que você deseja incluir na chave composta, o nome do tipo Tupla pode ficar bem longo, mas você pode tornar o nome mais curto criando sua própria classe derivada de Tupla <...>.
** editado em 2017 **
Há uma nova opção começando com C # 7: as tuplas de valor . A ideia é a mesma, mas a sintaxe é diferente, mais leve:
O tipo
Tuple<int, bool, string>
se torna(int, bool, string)
e o valorTuple.Create(4, true, "t")
se torna(4, true, "t")
.Com tuplas de valor, também é possível nomear os elementos. Observe que as performances são ligeiramente diferentes, então você pode querer fazer alguns benchmarking se forem importantes para você.
fonte
KeyValuePair<K,V>
e outros structs têm uma função hash padrão que é conhecida por ser ruim (consulte stackoverflow.com/questions/3841602/… para obter mais detalhes).Tuple<>
no entanto, não é um ValueType e sua função hash padrão pelo menos usará todos os campos. Dito isso, se o principal problema do seu código são as colisões, implemente um otimizadoGetHashCode()
que se adapte aos seus dados.A melhor maneira que eu poderia pensar é criar uma estrutura CompositeKey e certificar-se de substituir os métodos GetHashCode () e Equals () para garantir velocidade e precisão ao trabalhar com a coleção:
Um artigo MSDN sobre GetHashCode ():
http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx
fonte
Que tal
Dictionary<int, Dictionary<int, Dictionary<DateTime, MyClass>>>
?Isso permitiria que você:
fonte
CompositeDictionary<TKey1, TKey2, TValue>
(etc) que simplesmente herda deDictionary<TKey1, Dictionary<TKey2, TValue>>
(ou quantos dicionários aninhados são necessários. Sem implementar o tipo inteiro desde o início (em vez de trapacear usando dicionários ou tipos aninhados para conter as chaves) este é o mais rápido que conseguimos.Você pode armazená-los em uma estrutura e usá-la como a chave:
Link para obter o código hash: http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx
fonte
Tuple
s, então essa é uma boa solução!Agora que o VS2017 / C # 7 foi lançado, a melhor resposta é usar ValueTuple:
Decidi declarar o dicionário com um ValueTuple anônimo
(string, string, int)
. Mas eu poderia ter dado nomes a eles(string name, string path, int id)
.Perfwise, o novo ValueTuple é mais rápido do que Tuple em,
GetHashCode
mas mais lento emEquals
. Acho que você precisa fazer experimentos completos de ponta a ponta para descobrir qual é realmente o mais rápido para o seu cenário. Mas a gentileza de ponta a ponta e a sintaxe da linguagem para ValueTuple o fazem vencer.fonte
Duas abordagens vêm imediatamente à mente:
Faça como Kevin sugeriu e escreva uma estrutura que servirá como sua chave. Certifique-se de fazer essa estrutura implementar
IEquatable<TKey>
e substituir seus métodosEquals
eGetHashCode
*.Escreva uma classe que utilize dicionários aninhados internamente. Algo como:
TripleKeyDictionary<TKey1, TKey2, TKey3, TValue>
... esta classe seria internamente ter um membro do tipoDictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>>
, e iria expor métodos, comothis[TKey1 k1, TKey2 k2, TKey3 k3]
,ContainsKeys(TKey1 k1, TKey2 k2, TKey3 k3)
, etc.* Uma palavra sobre se a substituição do
Equals
método é necessária: embora seja verdade que oEquals
método para uma estrutura compara o valor de cada membro por padrão, ele o faz usando reflexão - que inerentemente envolve custos de desempenho - e, portanto, não é muito implementação apropriada para algo que deve ser usado como uma chave em um dicionário (na minha opinião, pelo menos). De acordo com a documentação do MSDN sobreValueType.Equals
:fonte
Se a chave fizer parte da classe, use
KeyedCollection
.É
Dictionary
onde a chave é derivada do objeto.Nos bastidores é o Dicionário.
Não é necessário repetir a chave no
Key
eValue
.Por que arriscar, a chave não é a mesma
Key
que noValue
.Não precisa duplicar as mesmas informações na memória.
Classe KeyedCollection
Indexador para expor a chave composta
Quanto ao uso do tipo de valor fpr, a chave que a Microsoft especificamente recomenda contra ele.
ValueType.GetHashCode
Tuple
tecnicamente não é um tipo de valor, mas sofre do mesmo sintoma (colisões de hash) e não é um bom candidato para uma chave.fonte
HashSet<T>
apropriadaIEqualityComparer<T>
também seria uma opção. A propósito, acho que sua resposta atrairá votos se você puder alterar os nomes de sua turma e de outros membros :)Posso sugerir uma alternativa - um objeto anônimo. É o mesmo que usamos no método GroupBy LINQ com várias chaves.
Pode parecer estranho, mas eu comparei Tuple.GetHashCode e os novos métodos {a = 1, b = 2} .GetHashCode e os objetos anônimos vencem em minha máquina no .NET 4.5.1:
Objeto - 89,1732 ms para 10.000 chamadas em 1.000 ciclos
Tupla - 738,4475 ms para 10.000 chamadas em 1.000 ciclos
fonte
dictionary[new { a = my_obj, b = 2 }]
, o código hash resultante será uma combinação de my_obj.GetHashCode e ((Int32) 2) .GetHashCode.Outra solução às já citadas seria armazenar algum tipo de lista de todas as chaves geradas até agora e quando um novo objeto for gerado você gera o seu hashcode (apenas como ponto de partida), verifique se já está na lista, se estiver é, em seguida, adicione algum valor aleatório etc. a ele até que você tenha uma chave exclusiva, então armazene essa chave no próprio objeto e na lista e retorne-a como a chave o tempo todo.
fonte