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


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.