Как вы получаете дубликат ключа, с которым произошла ошибка ToDictionary()?

#c# #dictionary #duplicates

#c# #словарь #дубликаты

Вопрос:

Я создаю объект Dictionary, используя IEnumerable метод ToDictionary() расширения:

 var dictionary = new Dictionary<string, MyType>
    (myCollection.ToDictionary<MyType, string>(k => k.Key));
  

При выполнении он выдает следующее ArgumentException :

Элемент с таким же ключом уже добавлен.

Как мне заставить его сообщить мне, что это за дубликат ключа?

Комментарии:

1. Это не ответ на ваш вопрос, но ToDictionary фактически создает словарь. Почему вы передаете этот словарь в конструктор другого словаря? И зачем указывать параметры универсального типа, которые вам не нужны? Я бы написал ваш пример без всего этого багажа как: var dictionary = myCollection.ToDictionary(x => x.Key);

2. 1. Потому что это одна из перегрузок конструктора для Dictionary. Но в чем-то вы правы.

3. 2. Предположительно, указание типов исключает любую возможность боксирования, но я не уверен в этом.

4. Возможно, это не идеальный способ, но вы можете перехватить ArgumentException и повторно создать его как новое исключение с дополнительной информацией, такой как ключ, причем исходное исключение является внутренним исключением для этого нового исключения.

5. @Robert — Компилятор определяет параметры универсального типа — если это невозможно, он сообщит вам об этом. В этом случае foo.ToDictionary(...) и foo.ToDictionary<MyType, string>(...) компиляция в точно такой же IL-код просто требуют меньше ввода.

Ответ №1:

Получите дубликаты ключей:

 var duplicateKeys =
  myCollection
 .GroupBy(k => k.Key)
 .Where(g => g.Count() > 1)
 .Select(g => g.Key);
  

Комментарии:

1. Кажется странным, что ArgumentException выданный ToDictionary() не включает в себя сбойный ключ. Насколько дорогостоящим является ваш запрос? Коллекция довольно большая.

2. @Роберт Харви, на самом деле это совершенно нормально: ArgumentException это очень общее исключение, оно не относится конкретно к словарям…

3. @Роберт Харви: Группировка довольно эффективна, но с большой коллекцией, конечно, все равно потребуется много работы. Это можно сделать более эффективно, используя Dictionary<string,int> и подсчитывая только вхождения каждого ключа вместо их группировки.

4. @ThomasLevesque: Нет причин, по которым исключения не могло быть throw new ArgumentException("An item with the same key has already been added. Key: " key.ToString()) Хотя toString() не будет иметь смысла для каждого ключа, бьюсь об заклад, это избавило бы от многих человеко-часов отладки всех разработчиков .NET Framework с небольшим недостатком.

5. @EricJ., да, это может быть включено в сообщение. Однако есть некоторые недостатки, хотя они не сразу очевидны. Я предлагаю вам прочитать это обсуждение на GitHub для получения более подробной информации.

Ответ №2:

Если в вашей конкретной ситуации можно вставить в ваш словарь только один из набора объектов с повторяющимися Key свойствами, вы можете полностью избежать этой ошибки, используя Distinct метод LINQ перед вызовом ToDictionary .

 var dict = myCollection.Distinct().ToDictionary(x => x.Key);
  

Конечно, вышеописанное будет работать только в том случае, если классы в вашей коллекции переопределяют Equals и GetHashCode таким образом, чтобы учитывалось только Key свойство. Если это не так, вам нужно будет создать пользовательский, IEqualityComparer<YourClass> который сравнивает только Key свойство.

 var comparer = new MyClassKeyComparer();
var dict = myCollection.Distinct(comparer).ToDictionary(x => x.Key);
  

Если вам нужно убедиться, что все экземпляры в вашей коллекции попадают в словарь, то использование Distinct у вас не сработает.

Комментарии:

1. Спасибо, идеальное решение для моей ситуации

2. Принятое решение получает дублированные ключи, а ваше просто предотвращает исключение. Отлично! Я выбираю ваш!

Ответ №3:

Сбойный ключ не включен, потому что общий словарь не гарантирует, что для типа ключа существует значимый метод toString. Вы могли бы создать класс-оболочку, который выдает более информативное исключение. Например:

 //Don't want to declare the key as type K because I assume _inner will be a Dictionary<string, V>
//public void Add(K key, V value)
//
public void Add(string key, V value)
{
    try
    {
        _inner.Add(key, value);
    }
    catch (ArgumentException e)
    {
        throw new ArgumentException("Exception adding key '"   key   "'", e);
    }
}
  

Комментарии:

1. Я должен был создать подпись void Add(string key, V value) , поскольку мое предложение предполагает, что вас интересуют только строковые ключи. Отредактированный ответ.

2. (‘поскольку общий словарь не имеет гарантии …’ Зачем ему нужна гарантия?)

3. @Iain Реализация по умолчанию ToString() возвращает имя типа объекта. Если бы в сообщении об исключении говорилось «данного ключа ‘{0}’ нет в словаре», есть очень большая вероятность, что в сообщении было бы сказано «данный ключ ‘MyNamespace. MyType’ отсутствует в словаре». Это бесполезно, поскольку все экземпляры этого типа имеют одинаковое строковое представление. Если бы существовала какая-то гарантия того, что тип предоставляет более значимое значение для toString, тогда подобное сообщение имело бы смысл.

Ответ №4:

Ключ, ArgumentException выдаваемый вызовом на Dictionary.Add , не содержит значения ключа. Вы могли бы очень легко добавить записи в словарь самостоятельно и предварительно выполнить отдельную проверку:

     var dictionary = new Dictionary<string, MyType>();
    foreach (var item in myCollection)
    {
        string key = item.Key;
        if (dictionary.ContainsKey(key))
        {
            // Handle error
            Debug.Fail(string.Format("Found duplicate key: {0}", key));
        }
        else
        {
            dictionary.Add(key, item);
        }
    }
  

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

Комментарии:

1. Да, это правда. Я думаю, я просто ленился с ToDictionary() методом.

2. Словарь. Add говорит, что он выдает дубликат ключа. Таким образом, вы могли бы (?) получить преимущество в производительности, выполнив Add сразу и отловив, если он выдает ошибку.

3. Намеренный перехват исключения почти всегда является плохой идеей с точки зрения производительности, выдача и перехват исключения настолько сложны, что в большинстве случаев вам придется повышать производительность, добавляя дополнительную проверку.

4. Хотя я согласен с тем, что следует помнить о стоимости создания исключения, здесь я не согласен. Функциональность по умолчанию уже генерирует исключение, поэтому его получение только для добавления дополнительной информации кажется правильным подходом. Кроме того, добавление дубликата в словарь является буквально исключительным случаем словаря. Наконец, я, конечно, не согласен с обменом исключения на Debug. Сбой, который будет удален при сборке выпуска, эффективно позволяя игнорировать дубликаты.