今天在使用 Linq 的 ToDictionary()
时发生了异常,提示:
System.ArgumentException: 已添加了具有相同键
因为以前一直没有遇到有相同键的情况,所以从来没关注过这个问题。然后写段试验来码来处理这个问题
问题再现
cs
class Program
{
public static void Main(string[] args)
{
var data = new[]{
Tuple.Create("001", "James"),
Tuple.Create("002", "Linda"),
Tuple.Create("003", "Frank"),
Tuple.Create("004", "Jack"),
Tuple.Create("002", "Rose"),
Tuple.Create("001", "Lynn"),
Tuple.Create("008", "Luke")
};
var dict = data.ToDictionary(t => t.Item1, t => t.Item2);
// 这里就抛异常了
// System.ArgumentException: 已添加了具有相同键的
foreach (var pair in dict)
{
// 不要见怪,用了 C# 6.0 的语法
Console.WriteLine($"{pair.Key} = {pair.Value}");
}
}
}
使用 ToLookup() 解决
原来 ToDictionary()
不会处理重复键,也没有提供多的参数来处理重复键。想了一下,这种问题大概应该用 ToLookup()
来解决,所以改写了代码
cs
// var dict = data.ToDictionary(t => t.Item1, t => t.Item2);
// System.ArgumentException: 已添加了具有相同键的
var dict = data.ToLookup(t => t.Item1, t => t.Item2)
.ToDictionary(t => t.Key, t => t.First());
ToLookup()
返回的是一个 ILookup<>
接口,看定义是一个 IGrouping<>
接口的枚举。 IGrouping<>
本身也是一个 IEnumerable<>
,具有 Key
属性。因为 IGrouping<>
将具有同一个键的所有元素都包含在内了,所以在生成 Dictionary 的时候只取其中一个元素,根据上下文需要,可以取 First()
,或者 Last()
。
这是 ILookup<>
和 IGrouping<>
的定义
cs
public interface ILookup<TKey, TElement>
: IEnumerable<IGrouping<TKey, TElement>>, IEnumerable
public interface IGrouping<out TKey, out TElement>
: IEnumerable<TElement>, IEnumerable
自定义 ToDictionaryEx() 来解决
因为 ToLookup()
返回的结果包含太多枚举对象,所以不知道性能如何。如果不放心,可以自己写个 ToDictionaryEx()
来解决,当然也不敢保证性能就比 ToLookup()
好,要实验了才知道。
cs
static class ToDictionaryExtentions
{
public static IDictionary<TKey, TValue> ToDictionaryEx<TElement, TKey, TValue>(
this IEnumerable<TElement> source,
Func<TElement, TKey> keyGetter,
Func<TElement, TValue> valueGetter)
{
IDictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>();
foreach (var e in source)
{
var key = keyGetter(e);
if (dict.ContainsKey(key))
{
continue;
}
dict.Add(key, valueGetter(e));
}
return dict;
}
}
现在实验代码又该改改了
cs
// var dict = data.ToDictionary(t => t.Item1, t => t.Item2);
// System.ArgumentException: 已添加了具有相同键的
//var dict = data.ToLookup(t => t.Item1, t => t.Item2)
// .ToDictionary(t => t.Key, t => t.First());
var dict = data.ToDictionaryEx(t => t.Item1, t => t.Item2);
性能
没有测试性能的问题,等暴露了性能问题再来测试吧