Доступ без учета регистра для универсального словаря

244

У меня есть приложение, которое использует управляемые DLL. Один из этих dll возвращает общий словарь:

Dictionary<string, int> MyDictionary;  

Словарь содержит ключи с прописными и строчными буквами.

С другой стороны, я получаю список потенциальных ключей (строка), однако я не могу гарантировать случай. Я пытаюсь получить значение в словаре, используя ключи. Но, конечно, следующее не получится, так как у меня несоответствие регистра:

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

Я надеялся, что у TryGetValue будет флаг игнорирования, как упомянуто в документе MSDN , но, похоже, это недопустимо для универсальных словарей.

Есть ли способ получить значение этого словаря, игнорируя регистр ключей? Есть ли лучший обходной путь, чем создание новой копии словаря с соответствующим параметром StringComparer.OrdinalIgnoreCase ?

TocToc
источник

Ответы:

515

Там нет никакого способа указать StringComparerв точке, где вы пытаетесь получить значение. Если вы думаете об этом, "foo".GetHashCode()и "FOO".GetHashCode()все совершенно по-другому, то нет никакого разумного способа реализовать регистронезависимое попадание на чувствительную к регистру хэш-карту.

Однако вы можете создать словарь без учета регистра в первую очередь, используя: -

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

Или создайте новый нечувствительный к регистру словарь с содержимым существующего чувствительного к регистру словаря (если вы уверены, что нет коллизий):

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

Этот новый словарь затем использует GetHashCode()реализацию StringComparer.OrdinalIgnoreCaseтак comparer.GetHashCode("foo")и comparer.GetHashcode("FOO")дает вам то же значение.

В качестве альтернативы, если в словаре всего несколько элементов и / или вам нужно искать только один или два раза, вы можете рассматривать исходный словарь как IEnumerable<KeyValuePair<TKey, TValue>>и просто перебирать его: -

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

Или, если вы предпочитаете, без 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;
  }
}

Это экономит затраты на создание новой структуры данных, но взамен стоимость поиска составляет O (n) вместо O (1).

Йен Галлоуэй
источник
На самом деле это имеет смысл. Большое спасибо за объяснение.
TocToc
1
Нет смысла хранить старый словарь и создавать новый экземпляр, так как любые случайные столкновения приведут к его взрыву. Если вы знаете, что не будете сталкиваться с конфликтами, то с самого начала вы можете использовать регистр без учета регистра.
Рис Бевилаква
2
Прошло уже десять лет, как я использую .NET, и я только что понял это !! Почему вы используете Ordinal вместо CurrentCulture?
Джордан,
Ну, это зависит от поведения, которое вы хотите. Если пользователь предоставляет ключ через пользовательский интерфейс (или если вам необходимо учитывать, например, ss и ß равные), вам нужно будет использовать другую культуру, но, учитывая, что значение используется в качестве ключа для хэш-карты, полученной из внешняя зависимость, я думаю, «OrdinalCulture» - разумное предположение.
Иэн Галлоуэй,
1
default(KeyValuePair<T, U>)не null- это KeyValuePairгде Key=default(T)и Value=default(U). Таким образом, вы не можете использовать ?.оператор в примере LINQ; вам нужно взять FirstOrDefault()и затем (для этого конкретного случая) проверить, еслиKey == null .
Ашербер
38

Для вас, LINQers, которые никогда не используют обычный словарь конструктора:

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)
Дерпи
источник
8

Это не очень элегантно, но в случае, если вы не можете изменить создание словаря, и все, что вам нужно, это грязный хак, как насчет этого:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }
Shoham
источник
13
или просто так: новый словарь <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Иордания
6
Согласно «Рекомендации по использованию строк в .NET Framework» используйте ToUpperInvariantвместо ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Фред
Это было хорошо для меня, когда мне приходилось ретроспективно проверять ключи нечувствительным образом. Я упростила это немного большеvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Джей
Почему не просто dict.Keys.Contains("bla", appropriate comparer)? Кроме того, вы не получите null для FirstOrDefault, поскольку пара ключей в C # является структурой.
Навфал