Неактуальний доступ до загального словника


244

У мене є додаток, який використовує керовані dlls. Один із цих dll повертає загальний словник:

Dictionary<string, int> MyDictionary;  

Словник містить клавіші з верхнього та нижнього регістру.

З іншого боку я отримую список потенційних ключів (рядка), однак я не можу гарантувати справу. Я намагаюся отримати значення у словнику за допомогою клавіш. Але, звичайно, наступне не вдасться, оскільки у мене є невідповідність справи:

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

Я сподівався, що TryGetValue матиме прапор ігнорування випадку, як згадується в документі MSDN , але, здається, це не вірно для загальних словників.

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


Відповіді:


514

Немає можливості вказати 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
Немає ніяких причин тримати старий словник довкола та інстанціювати новий, оскільки будь-які випадки зіткнення змусять його вибухнути. Якщо ви знаєте, що не зіткнетеся, то з самого початку ви також можете використовувати нечутливі регістри.
Rhys Bevilaqua

2
Минуло десять років, як я використовую .NET, і тепер я лише зрозумів це !! Чому ви використовуєте Ordinal замість CurrentCulture?
Йорданія

Ну, це залежить від поведінки, яку ви хочете. Якщо користувач надає ключ через інтерфейс користувача (або якщо вам потрібно вважати, наприклад, ss і ß рівними), вам потрібно буде використовувати іншу культуру, але враховуючи, що значення використовується як ключ для хешмапу, що надходить з зовнішня залежність, я вважаю, що «звичайна культура» є розумним припущенням.
Ієн Галлоуей

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;
    }

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());
Jay

Чому б не просто dict.Keys.Contains("bla", appropriate comparer)? Крім того, ви не отримаєте null для FirstOrDefault, оскільки параметр keyvalue в C # - це структура.
nawfal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.