【问题标题】:How do you get the duplicate key that ToDictionary() has failed on?你如何获得 ToDictionary() 失败的重复键?
【发布时间】:2011-07-31 05:33:10
【问题描述】:

我正在创建一个 Dictionary 对象,使用IEnumerableToDictionary() 扩展方法:

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

当它执行时,它会抛出以下ArgumentException

已添加具有相同密钥的项目。

如何让它告诉我重复键是什么?

【问题讨论】:

  • 这不是您问题的答案,但 ToDictionary 实际上创建了一个字典。你为什么将该字典传递给另一个字典的构造函数?为什么要指定您不必指定的泛型类型参数?我会把你的例子写成:var dictionary = myCollection.ToDictionary(x =&gt; x.Key);
  • 1.因为这是 Dictionary 的构造函数重载之一。但你说得有道理。
  • 2.据推测,指定类型消除了装箱的任何可能性,但我不确定。
  • 这可能不是理想的方法,但您可以捕获 ArgumentException 并将其作为新异常重新抛出,并提供更多信息,例如密钥,原始异常是这个新异常的内部异常。
  • @Robert - 编译器推断出泛型类型参数 - 如果不能,它会让你知道。在这种情况下,foo.ToDictionary(...)foo.ToDictionary&lt;MyType, string&gt;(...) 编译为完全相同的 IL 代码,只是需要更少的输入。

标签: c# dictionary duplicates


【解决方案1】:

获取重复键:

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

【讨论】:

  • ToDictionary() 抛出的 ArgumentException 不包含失败的密钥似乎很奇怪。您的查询成本是多少?该系列相当大。
  • @Robert Harvey,实际上这很正常:ArgumentException 是一个非常普遍的例外,它不是特定于字典...
  • @Robert Harvey:分组是相当有效的,但是对于大量的集合,它当然仍然需要大量的工作。使用Dictionary&lt;string,int&gt; 可以更有效地完成它,并且只计算每个键的出现次数而不是对它们进行分组。
  • @ThomasLevesque: @ThomasLevesque: 没有理由不会是 throw new ArgumentException("An item with the same key has already been added. Key: " + key.ToString()) 虽然 ToString() 对每个键都没有意义,但我敢打赌它会消除整个 .NET Framework 中的许多调试工时几乎没有缺点的开发人员。
  • @EricJ.,是的,它可以包含在消息中。然而,也有 的缺点,尽管它们不是很明显。我建议您阅读this discussion on GitHub 了解更多详情。
【解决方案2】:

如果您的具体情况允许只将具有重复 Key 属性的一组对象中的一个插入到您的字典中,则可以通过在调用 ToDictionary 之前使用 LINQ Distinct 方法完全避免此错误。

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

当然,只有当您的集合中的类以仅考虑 Key 属性的方式覆盖 EqualsGetHashCode 时,上述方法才有效。如果不是这种情况,您需要创建一个仅比较 Key 属性的自定义 IEqualityComparer&lt;YourClass&gt;

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

如果您需要确保集合中的所有实例最终都在字典中,那么使用 Distinct 将不适合您。

【讨论】:

  • 谢谢,完美解决我的情况
  • 接受的解决方案获取复制的密钥,而您的解决方案只是防止异常。好的!我选你的!
【解决方案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);
    }
}

【讨论】:

  • 我应该签名void Add(string key, V value),因为我的建议假设您只对字符串键感兴趣。答案已编辑。
  • ('因为通用字典没有保证...' 为什么需要保证?)
  • @Iain ToString() 的默认实现返回对象类型的名称。如果异常消息说“给定键 '{0}' 不在字典中”,则消息很有可能会说“给定键 'MyNamespace.MyType' 不在字典中”。这没有帮助,因为该类型的 all 实例具有相同的字符串表示形式。如果有某种保证该类型为 ToString 提供了更有意义的值,那么这样的消息将是有意义的。
【解决方案4】:

调用Dictionary.Add 引发的ArgumentException 不包含键值。您可以自己轻松地将条目添加到字典中,并事先进行明确的检查:

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

这个额外的检查应该相当便宜,因为元素是通过散列存储的。

【讨论】:

  • 是的,没错。我想我只是懒得使用ToDictionary() 方法。
  • Dictionary.Add 说它会抛出一个重复的键。因此,您可能(?)通过直接执行 Add 并捕获它是否抛出来获得性能优势。
  • 故意捕获异常在性能方面几乎总是一个坏主意,抛出和捕获异常是如此复杂,以至于大多数时候您将通过添加额外的检查来提高性能。
  • 虽然我同意应该注意抛出异常的成本,但我不同意这里。默认功能已经抛出异常,因此捕获它只是为了添加更多信息似乎是正确的方法。此外,将副本添加到字典实际上是字典的例外情况。最后,我当然不同意用 Debug.Fail 交易异常,它将在发布版本中删除,从而有效地允许忽略重复。
猜你喜欢
  • 1970-01-01
  • 2020-03-08
  • 2011-07-06
  • 1970-01-01
  • 2015-12-29
  • 1970-01-01
  • 2014-01-20
  • 1970-01-01
  • 2013-05-10
相关资源
最近更新 更多