Acesso sem distinção entre maiúsculas e minúsculas para dicionário genérico


244

Eu tenho um aplicativo que usa dlls gerenciadas. Uma dessas DLLs retorna um dicionário genérico:

Dictionary<string, int> MyDictionary;  

O dicionário contém teclas com maiúsculas e minúsculas.

Por outro lado, estou recebendo uma lista de chaves em potencial (string), mas não posso garantir o caso. Estou tentando obter o valor no dicionário usando as teclas. Mas é claro que o seguinte falhará, pois tenho uma incompatibilidade de casos:

bool Success = MyDictionary.TryGetValue( MyIndex, out TheValue );  

Eu esperava que o TryGetValue tivesse um sinalizador de maiúsculas e minúsculas como mencionado no documento do MSDN , mas parece que isso não é válido para dicionários genéricos.

Existe uma maneira de obter o valor desse dicionário ignorando o caso principal? Existe uma solução melhor do que criar uma nova cópia do dicionário com o parâmetro StringComparer.OrdinalIgnoreCase apropriado ?


Respostas:


514

Não há como especificar a StringComparerno ponto em que você tenta obter um valor. Se você pensar sobre isso "foo".GetHashCode()e "FOO".GetHashCode()for totalmente diferente, não haverá uma maneira razoável de implementar uma obtenção que não diferencia maiúsculas de minúsculas em um mapa de hash que diferencia maiúsculas de minúsculas.

No entanto, você pode criar um dicionário que não diferencia maiúsculas de minúsculas usando: -

var comparer = StringComparer.OrdinalIgnoreCase;
var caseInsensitiveDictionary = new Dictionary<string, int>(comparer);

Ou crie um novo dicionário que não diferencia maiúsculas de minúsculas com o conteúdo de um dicionário que diferencia maiúsculas de minúsculas existente (se tiver certeza de que não há colisão de maiúsculas e minúsculas): -

var oldDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var newDictionary = new Dictionary<string, int>(oldDictionary, comparer);

Este novo dicionário, em seguida, usa a GetHashCode()implementação no StringComparer.OrdinalIgnoreCasemodo comparer.GetHashCode("foo")e comparer.GetHashcode("FOO")dar-lhe o mesmo valor.

Como alternativa, se houver apenas alguns elementos no dicionário e / ou você precisar pesquisar apenas uma ou duas vezes, poderá tratar o dicionário original como um IEnumerable<KeyValuePair<TKey, TValue>>e apenas iterá-lo: -

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var value = myDictionary.FirstOrDefault(x => String.Equals(x.Key, myKey, comparer)).Value;

Ou, se preferir, sem o LINQ: -

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
int? value;
foreach (var element in myDictionary)
{
  if (String.Equals(element.Key, myKey, comparer))
  {
    value = element.Value;
    break;
  }
}

Isso economiza o custo de criação de uma nova estrutura de dados, mas, em troca, o custo de uma pesquisa é O (n) em vez de O (1).


Na verdade, faz sentido. Muito obrigado pela explicação.
TocToc 5/11/12

1
Não há razão para manter o dicionário antigo por perto e instanciar o novo, pois qualquer colisão de casos fará com que ele exploda. Se você sabe que não terá colisões, poderá usar maiúsculas e minúsculas desde o início.
Rhys Bevilaqua

2
Faz dez anos que uso o .NET e agora descobri isso! Por que você usa Ordinal em vez de CurrentCulture?
Jordânia

Bem, isso depende do comportamento que você deseja. Se o usuário estiver fornecendo a chave por meio da interface do usuário (ou se você precisar considerar, por exemplo, ss e ß iguais), precisará usar uma cultura diferente, mas, considerando que o valor está sendo usado como a chave para um hashmap proveniente de uma dependência externa, acho que 'OrdinalCulture' é uma suposição razoável.
Iain Galloway

1
default(KeyValuePair<T, U>)não é null- é um KeyValuePaironde Key=default(T)e Value=default(U). Portanto, você não pode usar o ?.operador no exemplo LINQ; você precisará pegar FirstOrDefault()e, em seguida (neste caso específico), verificar se Key == null.
Asherber

38

Para você, LINQers por aí que nunca usam um construtor de dicionário comum:

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)

8

Não é muito elegante, mas caso você não possa alterar a criação do dicionário, e tudo o que você precisa é de um truque sujo, e quanto a isso:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }

13
ou apenas isso: new Dictionary <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Jordânia

6
Conforme "Práticas recomendadas para usar seqüências de caracteres no .NET Framework", use em ToUpperInvariantvez de ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Fred

Isso foi bom para mim, onde tive que verificar as chaves retrospectivamente de maneira insensível. I racionalizado um pouco maisvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay

Por que não apenas dict.Keys.Contains("bla", appropriate comparer)? Além disso, você não obterá nulo para FirstOrDefault, pois keyvaluepair em C # é uma estrutura.
Nawfal
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.