今天在使用 Linq 的 ToDictionary()
時發生了異常,提示:
System.ArgumentException: 已新增了具有相同鍵
因為以前一直沒有遇到有相同鍵的情況,所以從來沒關注過這個問題。然後寫段試驗來碼來處理這個問題
問題再現
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()
來解決,所以改寫了程式碼
// 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<>
的定義public interface ILookup<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
自定義 ToDictionaryEx() 來解決
因為 ToLookup()
返回的結果包含太多列舉物件,所以不知道效能如何。如果不放心,可以自己寫個 ToDictionaryEx()
來解決,當然也不敢保證效能就比 ToLookup()
好,要實驗了才知道。
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;
}
}
現在實驗程式碼又該改改了
// 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);
效能
沒有測試效能的問題,等暴露了效能問題再來測試吧