Groß- und Kleinschreibung wird für generisches Wörterbuch nicht berücksichtigt

243

Ich habe eine Anwendung, die verwaltete DLLs verwendet. Eine dieser DLLs gibt ein generisches Wörterbuch zurück:

Dictionary<string, int> MyDictionary;  

Das Wörterbuch enthält Schlüssel in Groß- und Kleinschreibung.

Auf einer anderen Seite erhalte ich eine Liste möglicher Schlüssel (Zeichenfolge), kann jedoch den Fall nicht garantieren. Ich versuche, den Wert im Wörterbuch mit den Schlüsseln zu erhalten. Aber natürlich wird Folgendes fehlschlagen, da ich eine Fallinkongruenz habe:

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

Ich hatte gehofft, dass TryGetValue ein Ignorier-Fall- Flag haben würde, wie im MSDN-Dokument erwähnt , aber es scheint, dass dies für generische Wörterbücher nicht gültig ist.

Gibt es eine Möglichkeit, den Wert dieses Wörterbuchs zu ermitteln, indem der Schlüsselfall ignoriert wird? Gibt es eine bessere Problemumgehung als das Erstellen einer neuen Kopie des Wörterbuchs mit dem richtigen Parameter StringComparer.OrdinalIgnoreCase ?

Klopf klopf
quelle

Antworten:

513

Es gibt keine Möglichkeit, a StringCompareran dem Punkt anzugeben, an dem Sie versuchen, einen Wert zu erhalten. Wenn Sie darüber nachdenken "foo".GetHashCode()und "FOO".GetHashCode()völlig anders sind, gibt es keine vernünftige Möglichkeit, einen auf Groß- und Kleinschreibung nicht berücksichtigten Zugriff auf eine Hash-Map mit Groß- und Kleinschreibung zu implementieren.

Sie können jedoch zunächst ein Wörterbuch ohne Berücksichtigung der Groß- und Kleinschreibung erstellen, indem Sie:

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

Oder erstellen Sie ein neues Wörterbuch ohne Berücksichtigung der Groß- und Kleinschreibung mit dem Inhalt eines vorhandenen Wörterbuchs ohne Berücksichtigung der Groß- und Kleinschreibung (wenn Sie sicher sind, dass keine Kollisionen zwischen Groß- und Kleinschreibung vorliegen): -

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

Dieses neue Wörterbuch verwendet dann die GetHashCode()Implementierung auf StringComparer.OrdinalIgnoreCaseso comparer.GetHashCode("foo")und comparer.GetHashcode("FOO")gibt Ihnen den gleichen Wert.

Wenn das Wörterbuch nur wenige Elemente enthält und / oder Sie nur ein- oder zweimal nachschlagen müssen, können Sie das ursprüngliche Wörterbuch als IEnumerable<KeyValuePair<TKey, TValue>>und behandeln und es einfach durchlaufen: -

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

Oder wenn Sie es vorziehen, ohne den 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;
  }
}

Dies erspart Ihnen die Kosten für die Erstellung einer neuen Datenstruktur. Im Gegenzug betragen die Kosten für eine Suche jedoch O (n) anstelle von O (1).

Iain Galloway
quelle
In der Tat macht es Sinn. Vielen Dank für die Erklärung.
TocToc
1
Es gibt keinen Grund, das alte Wörterbuch beizubehalten und das neue zu instanziieren, da es bei Fallkollisionen explodieren kann. Wenn Sie wissen, dass Sie keine Kollisionen bekommen, können Sie von Anfang an auch die Groß- und Kleinschreibung nicht berücksichtigen.
Rhys Bevilaqua
2
Es ist zehn Jahre her, dass ich .NET benutze und ich habe es jetzt gerade herausgefunden !! Warum verwenden Sie Ordinal anstelle von CurrentCulture?
Jordanien
Nun, es hängt von dem Verhalten ab, das Sie wollen. Wenn der Benutzer den Schlüssel über die Benutzeroberfläche bereitstellt (oder wenn Sie z. B. ss und ß als gleich betrachten müssen), müssen Sie eine andere Kultur verwenden, vorausgesetzt, der Wert wird als Schlüssel für eine Hashmap verwendet, die von kommt Als externe Abhängigkeit halte ich 'OrdinalCulture' für eine vernünftige Annahme.
Iain Galloway
1
default(KeyValuePair<T, U>)ist nicht null- es ist ein KeyValuePairwo Key=default(T)und Value=default(U). Sie können den ?.Operator im LINQ-Beispiel also nicht verwenden . Sie müssen greifen FirstOrDefault()und dann (für diesen speziellen Fall) prüfen, ob Key == null.
Asherber
38

Für Sie LINQer da draußen, die niemals einen regulären Wörterbuchkonstruktor verwenden:

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

Es ist nicht sehr elegant, aber falls Sie die Erstellung des Wörterbuchs nicht ändern können und alles, was Sie brauchen, ist ein schmutziger Hack, wie wäre es damit:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }
Shoham
quelle
13
oder einfach so: neues Wörterbuch <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Jordanien
6
Verwenden Sie gemäß "Best Practices für die Verwendung von Zeichenfolgen in .NET Framework" ToUpperInvariantanstelle von ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Fred
Das war gut für mich, wo ich die Schlüssel unempfindlich nachträglich überprüfen musste. Ich habe es ein bisschen weiter gestrafftvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay
Warum nicht einfach dict.Keys.Contains("bla", appropriate comparer)? Darüber hinaus erhalten Sie für FirstOrDefault keine Null, da das Schlüsselwertpaar in C # eine Struktur ist.
Nawfal