题目很基础,为什么我不能:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
题目很基础,为什么我不能:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
对原问题的评论已经很好地概括了:
因为没有人设计,指定,实现,测试,文档化和交付该特性。 - @Gabe Moothart
至于为什么呢?嗯,很可能是因为合并字典的行为无法按照框架指南的方式进行推理。
AddRange
不存在,因为对于关联容器来说,范围没有任何意义,因为数据范围允许有重复条目。例如,如果您有一个IEnumerable<KeyValuePair<K,T>>
这个集合不会防止重复项。
添加键值对集合,甚至合并两个字典的行为很直观。然而,如何处理多个重复条目的行为则不是那么明确。
当方法处理重复项时,应该是什么行为呢?
我能想到至少三种解决方案:
当抛出异常时,原始字典的状态应该是什么?
Add
几乎总是实现为原子操作:它成功并更新了集合的状态,或者失败,集合的状态保持不变。由于AddRange
可能由于重复错误而失败,因此使其行为与Add
一致的方法是通过在任何重复项上抛出异常来使其成为原子操作,并将原始字典的状态保持不变。
作为API使用者,逐个删除重复元素会很繁琐,这意味着AddRange
应该抛出一个包含所有重复值的单个异常。
然后选择就只有:
有支持两种用例的观点。为了实现这一点,您需要在签名中添加一个IgnoreDuplicates
标志吗?
IgnoreDuplicates
标志(设置为true时)也会提供显著的速度提升,因为底层实现将绕过重复检查的代码。
现在,您有一个允许AddRange
支持两种情况的标志,但是存在未记录的副作用(这是框架设计人员非常努力避免的事情)。
摘要
由于处理重复项时没有明确、一致和预期的行为,因此最好一起不处理它们,并且不提供该方法。
如果您发现自己不断地必须合并字典,当然可以编写自己的扩展方法来合并字典,以适合您的应用程序。
AddMultiple
与 AddRange
不同,但无论如何实现都会有些问题:当遇到重复键时,是使用包含所有重复键的数组来抛出异常?还是在遇到第一个重复键时立即抛出异常?如果抛出异常,字典的状态应该是什么?是原始状态还是已成功添加的所有键值对? - AlanDictionary<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);
}
...
namespace MyProject.Helper
{
public static class CollectionHelper
{
public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic[x.Key] = x.Value);
}
public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
}
public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
}
public static bool ContainsKeys<TKey, TValue>(this IDictionary<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;
}
}
}
}
玩得开心。
ToList()
,因为字典本身就是一个 IEnumerable<KeyValuePair<TKey,TValue>
。另外,如果你添加了一个已经存在的键值对,第二个和第三个方法都会抛出异常。这并不是一个好主意,你是否想要使用 TryAdd
?最后,第二个方法可以被替换为 Where(pair->!dic.ContainsKey(pair.Key)...
。 - Panagiotis KanavosToList()
不是一个好的解决方案,所以我已经改变了代码。如果你不确定第三种方法,你可以使用 try { mainDic.AddRange(addDic); } catch { do something }
。第二种方法完美地工作。 - ADM-IT如果有人像我一样遇到这个问题 - 可以使用IEnumerable扩展方法来实现"AddRange":
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
在合并字典时主要的技巧是处理重复的键值。在上面的代码中,这就是.Select(grp => grp.First())
部分。 在这种情况下,它只是从重复组中取第一个元素,但如果需要,您可以在那里实现更复杂的逻辑。
dict1
没有使用默认的相等比较器会怎样? - mjwillsvar combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
- Kyle McClellan我的猜测是缺乏向用户适当输出发生了什么的内容。 由于字典中不能有重复的键,那么当两个字典有部分键相交时,你该如何处理合并这两个字典呢?当然,你可以说:“我不在乎”,但这会违反对于重复键应该返回false/抛出异常的惯例。
NamedElementException
??),抛出它而不是 ArgumentException 或作为其 innerException,指定冲突的命名元素... 有几种不同的选择,我想说。 - Code Jockey你可以这样做
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
,它来自OP。请再次检查问题和我的答案。 - Valamas随意使用扩展方法,例如:
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;
}
只需使用Concat()
函数:
dic.Concat(MethodThatReturnAnotherDic());
CopyTo
。
List.AddRange
使用Array.Copy
。这类似于Buffer.BlockCopy
,其在C语言中的等价物是memcpy
。它存在的目的是为了对大块连续数据进行高效复制,而你需要的是所有这些数据。将这些数据添加到listA
的末尾很容易实现,但合并两个字典的条目并不那么简单。而且你的建议需要解决这个问题。AddRange
是不准确的。我可能会称其为AddEntries
。任何实现都需要解决一些有趣的问题,其中一些在这里已经被其他人评论过了。Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
。 - Panagiotis Kanavos如果您知道不会出现重复的键,可以这样做:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
如果有重复的键/值对,它会抛出异常。
我不知道为什么这个没有被包含在框架中;应该被包含。没有不确定性;只需要抛出一个异常。在这段代码的情况下,它确实抛出了一个异常。
var caseInsensitiveDictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
创建原始字典会怎样? - mjwills
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));
正如其他人所提到的,您可能需要小心处理重复项。 - Ama