解決Linq.ToDictionary()時的鍵重複問題

邊城發表於2019-02-16

今天在使用 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);

效能

沒有測試效能的問題,等暴露了效能問題再來測試吧

相關文章