关于c#:为什么Dictionary没有AddRange?

Why doesn't Dictionary have AddRange?

标题足够基本,为什么我不能:

1
2
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());


对原始问题的评论很好地总结了这一点:

because no one ever designed, specified, implemented, tested, documented and shipped that feature. - @Gabe Moothart

为什么?嗯,可能是因为合并字典的行为不能以符合框架指南的方式进行推理。

AddRange不存在,因为范围对关联容器没有任何意义,因为数据范围允许重复条目。例如,如果您有一个IEnumerable>,则该托收不防范重复条目。

添加一组键值对,甚至合并两个字典的行为是直接的。但是,如何处理多个重复条目的行为则不是。

当处理重复项时,该方法的行为应该是什么?

我至少可以想到三种解决方案:

  • 对第一个重复项引发异常
  • 引发包含所有重复项的异常
  • 忽略重复项
  • 当抛出异常时,原始字典的状态应该是什么?

    Add几乎总是作为一个原子操作来实现:它成功并更新集合的状态,或者失败,并且集合的状态保持不变。由于AddRange可能由于重复错误而失败,因此保持其行为与Add一致的方法是通过在任何重复上抛出异常使其成为原子,并保持原始字典的状态不变。

    作为一个API使用者,必须迭代地删除重复的元素会很麻烦,这意味着AddRange应该抛出一个包含所有重复值的异常。

    然后选择归结为:

  • 对所有重复项抛出异常,只保留原始字典。
  • 忽略重复项并继续。
  • 有支持这两个用例的参数。为此,您是否在签名中添加了IgnoreDuplicates标志?

    IgnoreDuplicates标志(当设置为true时)也将提供显著的加速,因为底层实现将绕过代码进行重复检查。

    现在,您有了一个标志,它允许AddRange支持这两种情况,但有一个未记录的副作用(这是框架设计者努力避免的)。

    总结

    由于在处理重复项时没有清晰、一致和预期的行为,因此更容易不一起处理它们,也不提供开始的方法。

    如果您发现自己不断地需要合并字典,那么您当然可以编写自己的扩展方法来合并字典,这将以适合您的应用程序的方式运行。


    我找到了另一种解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Dictionary<string, string> mainDic = new Dictionary<string, string>() {
        {"Key1","Value1" },
        {"Key2","Value2.1" },
    };
    Dictionary<string, string> additionalDic= new Dictionary<string, string>() {
        {"Key2","Value2.2" },
        {"Key3","Value3" },
    };
    mainDic.AddRangeOverride(additionalDic); // Overrides all existing keys
    // or
    mainDic.AddRangeNewOnly(additionalDic); // Adds new keys only
    // or
    mainDic.AddRange(additionalDic); // Throws an error if keys already exist
    // or
    if (!mainDic.ContainsKeys(additionalDic.Keys)) // Checks if keys don't exist
    {
        mainDic.AddRange(additionalDic);
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    namespace MyProject.Helper
    {
        public static void AddRangeOverride<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
        {
            dicToAdd.ForEach(x => dic[x.Key] = x.Value);
        }

        public static void AddRangeNewOnly<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
        {
            dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
        }

        public static void AddRange<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
        {
            dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
        }

        public static bool ContainsKeys<TKey, TValue>(this Dictionary<TKey, TValue> dic, IEnumerable<TKey> keys)
        {
            bool result = false;
            keys.ForEachOrBreak((x) => { result = dic.ContainsKey(x); return result; });
            return result;
        }

        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach (var item in source)
                action(item);
        }

        public static void ForEachOrBreak<T>(this IEnumerable<T> source, Func<T, bool> func)
        {
            foreach (var item in source)
            {
                bool result = func(item);
                if (result) break;
            }
        }

    }

    玩得高兴。


    我的猜测是,对于发生的事情,没有适当的输出给用户。由于字典中不能有重复键,因此如何处理在某些键相交的地方合并两个字典?当然,你可以说:"我不在乎",但这违反了返回错误/抛出重复键例外的惯例。


    如果有人像我一样遇到这个问题-可以使用IEnumerable扩展方法实现"addrange":

    1
    2
    3
    4
    5
    var combined =
        dict1.Union(dict2)
            .GroupBy(kvp => kvp.Key)
            .Select(grp => grp.First())
            .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

    组合字典的主要技巧是处理重复的键。在上面的代码中,它是.Select(grp => grp.First())部分。在这种情况下,它只是从重复组中提取第一个元素,但是如果需要,您可以在那里实现更复杂的逻辑。


    你可以这样做

    1
    2
    3
    4
    5
    6
    7
    8
    Dictionary<string, string> dic = new Dictionary<string, string>();
    // dictionary other items already added.
    MethodThatReturnAnotherDic(dic);

    public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
    {
        dic.Add(.., ..);
    }

    或者使用addrange的列表和/或使用上面的模式。

    1
    List<KeyValuePair<string, string>>


    请随意使用如下扩展方法:

    1
    2
    3
    4
    5
    6
    7
    public static Dictionary<T, U> AddRange<T, U>(this Dictionary<T, U> destination, Dictionary<T, U> source)
    {
      if (destination == null) destination = new Dictionary<T, U>();
      foreach (var e in source)
        destination.Add(e.Key, e.Value);
      return destination;
    }


    如果您正在处理一个新的字典(并且您没有要丢失的现有行),则可以始终使用另一个对象列表中的toDictionary()。

    因此,在您的情况下,您可以这样做:

    1
    2
    Dictionary<string, string> dic = new Dictionary<string, string>();
    dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);


    如果你知道你不会有重复的钥匙,你可以这样做:

    1
    dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

    如果存在重复的键/值对,它将引发异常。

    我不知道为什么这不在框架中;应该是。没有不确定性;只是抛出一个例外。对于此代码,它确实会引发异常。