Accesso senza distinzione tra maiuscole e minuscole per dizionario generico


244

Ho un'applicazione che utilizza DLL gestite. Una di quelle DLL restituisce un dizionario generico:

Dictionary<string, int> MyDictionary;  

Il dizionario contiene chiavi con lettere maiuscole e minuscole.

Da un altro lato sto ottenendo un elenco di potenziali chiavi (stringa) ma non posso garantire il caso. Sto cercando di ottenere il valore nel dizionario usando i tasti. Ma ovviamente non riuscirò perché non ho una corrispondenza del caso:

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

Speravo che TryGetValue avrebbe un flag di caso ignorato come menzionato nel documento MSDN , ma sembra che questo non sia valido per i dizionari generici.

C'è un modo per ottenere il valore di quel dizionario ignorando il caso chiave? Esiste una soluzione alternativa rispetto alla creazione di una nuova copia del dizionario con il parametro StringComparer.OrdinalIgnoreCase corretto ?


Risposte:


514

Non è possibile specificare StringComparera nel punto in cui si tenta di ottenere un valore. Se ci pensate, "foo".GetHashCode()e "FOO".GetHashCode()siete totalmente diversi, quindi non esiste un modo ragionevole per implementare un metodo senza distinzione tra maiuscole e minuscole su una mappa hash sensibile al maiuscolo / minuscolo.

È possibile, tuttavia, creare un dizionario senza distinzione tra maiuscole e minuscole utilizzando innanzitutto: -

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

Oppure crea un nuovo dizionario senza distinzione tra maiuscole e minuscole con i contenuti di un dizionario con distinzione tra maiuscole e minuscole esistente (se sei sicuro che non vi siano collisioni tra maiuscole e minuscole): -

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

Questo nuovo dizionario utilizza quindi l' GetHashCode()implementazione e StringComparer.OrdinalIgnoreCasequindi offre lo stesso valore.comparer.GetHashCode("foo")comparer.GetHashcode("FOO")

In alternativa, se ci sono solo pochi elementi nel dizionario e / o hai solo bisogno di cercare una o due volte, puoi considerare il dizionario originale come un IEnumerable<KeyValuePair<TKey, TValue>>semplice iterare su di esso: -

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

O se preferisci, senza 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;
  }
}

Ciò consente di risparmiare il costo di creazione di una nuova struttura di dati, ma in cambio il costo di una ricerca è O (n) anziché O (1).


In effetti ha senso. Grazie mille per la spiegazione.
TocToc

1
Non vi è alcun motivo per mantenere il vecchio dizionario in giro e creare un'istanza di quello nuovo poiché eventuali collisioni causeranno l'esplosione. Se sai che non otterrai collisioni, puoi anche utilizzare la distinzione tra maiuscole e minuscole sin dall'inizio.
Rhys Bevilaqua,

2
Sono dieci anni che uso .NET e ora ho capito! Perché usi Ordinal invece di CurrentCulture?
Giordania,

Bene, dipende dal comportamento che desideri. Se l'utente fornisce la chiave tramite l'interfaccia utente (o se è necessario considerare eg ss e ß uguali), è necessario utilizzare una cultura diversa, ma dato che il valore viene utilizzato come chiave per una hashmap proveniente da una dipendenza esterna, penso che "OrdinalCulture" sia un presupposto ragionevole.
Iain Galloway,

1
default(KeyValuePair<T, U>)non è null- è un KeyValuePairdove Key=default(T)e Value=default(U). Quindi non è possibile utilizzare l' ?.operatore nell'esempio LINQ; dovrai prendere FirstOrDefault()e poi (per questo caso particolare) verificare se Key == null.
asherber

38

Per voi LINQ là fuori che non usano mai un normale costruttore di dizionari:

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

8

Non è molto elegante, ma nel caso in cui non riesci a cambiare la creazione del dizionario, e tutto ciò di cui hai bisogno è un trucco sporco, che ne dici di questo:

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

13
o semplicemente questo: nuovo dizionario <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Giordania,

6
Come da "Best practice per l'utilizzo di stringhe in .NET Framework" utilizzare ToUpperInvariantanziché ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Fred

Questo è stato un bene per me, dove ho dovuto controllare retrospettivamente le chiavi in ​​modo insensibile. L'ho semplificato un po 'di piùvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay,

Perché non solo dict.Keys.Contains("bla", appropriate comparer)? Inoltre, non otterrai null per FirstOrDefault poiché keyvaluepair in C # è una struttura.
nawfal,
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.