C# 如何获取ToDictionary()失败的重复密钥?

C# 如何获取ToDictionary()失败的重复密钥?,c#,dictionary,duplicates,C#,Dictionary,Duplicates,我正在使用IEnumerable的ToDictionary()扩展方法创建Dictionary对象: var dictionary = new Dictionary<string, MyType> (myCollection.ToDictionary<MyType, string>(k => k.Key)); var字典=新字典 (myCollection.ToDictionary(k=>k.Key)); 当它执行时,它抛出以下ArgumentExcep

我正在使用
IEnumerable
ToDictionary()
扩展方法创建Dictionary对象:

var dictionary = new Dictionary<string, MyType>
    (myCollection.ToDictionary<MyType, string>(k => k.Key));
var字典=新字典
(myCollection.ToDictionary(k=>k.Key));
当它执行时,它抛出以下
ArgumentException

已添加具有相同密钥的项


如何让它告诉我复制的密钥是什么?

获取复制的密钥:

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

不包括失败的键,因为泛型字典不能保证键类型上存在有意义的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);
    }
}
//不想将密钥声明为K类型,因为我假设_inner将是一个字典
//公共无效添加(K键,V值)
//
公共无效添加(字符串键,V值)
{
尝试
{
_添加(键、值);
}
捕获(e)
{
抛出新的ArgumentException(“异常添加键'”+键+””,e);
}
}

调用
字典引发的
ArgumentException
。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);
        }
    }
var dictionary=newdictionary();
foreach(myCollection中的var项)
{
字符串键=item.key;
if(字典容器(键))
{
//处理错误
Debug.Fail(string.Format(“找到重复的键:{0}”,键));
}
其他的
{
添加(键、项);
}
}

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

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

var dict = myCollection.Distinct().ToDictionary(x => x.Key);
当然,只有当集合中的类以只考虑
属性的方式重写
Equals
GetHashCode
时,上述操作才有效。如果不是这样,则需要创建一个自定义的
IEqualityComparer
,它只比较
属性

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


如果您需要确保集合中的所有实例都在字典中,那么使用
Distinct
对您无效。

ToDictionary()
引发的
ArgumentException
不包括失败的键,这似乎很奇怪。您的查询成本有多高?集合相当大。@Robert Harvey,事实上这是非常正常的:
ArgumentException
是一个非常普遍的例外,它并不特定于字典…@Robert Harvey:分组相当有效,但是对于一个大集合,当然仍然需要大量的工作。通过使用
字典
,只计算每个键的发生次数而不是对它们进行分组,可以更有效地执行此操作。@ThomasLevsque:没有理由该异常不能
抛出新的ArgumentException(“已经添加了具有相同键的项。键:“+key.ToString())
而ToString()不会对每个键都有意义,我打赌这将消除所有.NET Framework开发人员的许多人工时调试,几乎没有任何负面影响。@EricJ.,是的,它可以包含在消息中。然而,也有不利的一面,尽管它们不是很明显。我建议你阅读更多细节。这不是对你问题的回答,但ToDictionary实际上创建了一本字典。为什么要将该字典传递给另一个字典的构造函数?为什么不必指定泛型类型参数呢?我会将您的示例编写为:
var dictionary=myCollection.ToDictionary(x=>x.Key)1。因为这是字典的构造函数重载之一。但你确实有道理。大概,指定类型可以消除任何装箱的可能性,但我不确定。这可能不是理想的方式,但您可以捕获ArgumentException并使用更多信息(如密钥)将其作为新的异常重新提交,原始异常是新异常的内部异常。@Robert-编译器推断泛型类型参数-如果不能,它会让您知道。在这种情况下,
foo.ToDictionary(…)
foo.ToDictionary(…)
编译成完全相同的IL代码,只需要更少的键入。是的,这是真的。我想我只是懒得使用
ToDictionary()
method.Dictionary.Add说它会抛出一个重复的键。因此,通过直接添加并捕获抛出的异常,您可能会(?)获得性能优势。故意捕获异常几乎总是一个糟糕的想法,从性能角度来看,抛出和捕获异常非常复杂,大多数情况下,您都可以通过添加额外的检查来提高性能。虽然我同意应该注意抛出异常的成本,但我不同意这一点。默认功能已经抛出了一个异常,因此捕获它只是为了添加更多信息似乎是正确的方法。此外,在字典中添加副本是字典的特例。最后,我当然不同意使用Debug.Fail交易异常,它将在发布版本中删除,有效地允许忽略重复项。我应该让签名
void Add(string key,V value)
,因为我的建议假设您只对字符串键感兴趣。答案已编辑。(“因为通用字典没有保证…”为什么需要保证?@Iain
ToString()
的默认实现返回对象类型的名称。如果异常消息是说“给定的键“{0}”不在DICT中