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

性能

没有测试性能的问题,等暴露了性能问题再来测试吧


边城
59.8k 声望29.6k 粉丝

一路从后端走来,终于走在了前端!