如何遍历字典并更改值?
Posted
技术标签:
【中文标题】如何遍历字典并更改值?【英文标题】:How to iterate through Dictionary and change values? 【发布时间】:2011-01-16 15:44:42 【问题描述】:Dictionary<string,double> myDict = new Dictionary();
//...
foreach (KeyValuePair<string,double> kvp in myDict)
kvp.Value = Math.Round(kvp.Value, 3);
我收到一个错误:
“无法将属性或索引器‘System.Collections.Generic.KeyValuePair.Value’分配给——它是只读的。”
如何遍历 myDict
并更改值?
【问题讨论】:
如果从性能角度(间接和更多 gc 压力)可以接受添加大量引用对象并且您可以控制字典,然后为您的值创建一个框将解决问题,例如:class Box<T> public T boxed;
,然后使用Dictionary<string, Box<double>>
,然后您可以在 foreach 中“修改”类似:kvp.Value.boxed = 123.456;
。并不是说这是“最好”的方法,但它有它的用途。
这能回答你的问题吗? Editing dictionary values in a foreach loop
【参考方案1】:
根据MSDN:
foreach 语句是一个包装器 在枚举器周围,它允许 只从收藏中读取,而不是 写信给它。
使用这个:
var dictionary = new Dictionary<string, double>();
// TODO Populate your dictionary here
var keys = new List<string>(dictionary.Keys);
foreach (string key in keys)
dictionary[key] = Math.Round(dictionary[key], 3);
【讨论】:
我在 .NET 2 和 3.5 中测试了您的示例,它抛出“集合已修改异常”。请参阅:***.com/questions/1562729/… 这在 .NET 4 中是否发生了变化,还是您没有测试您的示例? 多么尴尬 - 我忽略了填充列表的部分。现在修好了。这里的想法是您可以更改字典条目的值,而不是它的引用。 希望您在问题注释行中保留“//...(填充)”。一眼看去,列表是空的。 顺便说一句,你可以做 dictionary.Keys.ToList() 在 .NET 5 中,您不再需要额外的列表。【参考方案2】:对于懒惰的程序员:
Dictionary<string, double> dictionary = new Dictionary<string, double>();
foreach (var key in dictionary.Keys.ToList())
dictionary[key] = Math.Round(dictionary[key], 3);
【讨论】:
由于我现在使用 .NET 4.5 进行编码,ToList()
方法不可用,但 Keys
成员是可迭代的,因此 .ToList()
是不必要的。
@MikeC 参见***.com/a/2260462/1037948 上的评论——你不能直接枚举键,.ToList()
是解决这个问题的“黑客”
ToList() 是一种 LINQ 扩展方法。如果您添加“使用 System.Linq;”,它应该可用到您的 using 语句。
@drzaus:你为什么不能枚举 Keys ? Keys 是一个字典,就像任何其他字典一样可以枚举。我在没有 .ToList() 的情况下这样做。
我知道这是一个旧的答案,但我更喜欢它而不是接受的答案 - 我想说与其懒惰它更简洁(删除明确声明键列表的行)。因此,要回答上面的问题:您可以枚举键集合,但问题是关于枚举和进行更改,这是您无法做到的。添加 ToList() 意味着您实际上是在枚举一个列表,该列表恰好包含与字典中的键相同的对象。这使得字典本身是可变的,因此允许您进行更改。【参考方案3】:
你不应该在迭代字典的时候改变它,否则你会得到一个异常。
所以首先将键值对复制到一个临时列表中,然后遍历这个临时列表,然后更改你的字典:
Dictionary<string, double> myDict = new Dictionary<string, double>();
// a few values to play with
myDict["a"] = 2.200001;
myDict["b"] = 77777.3333;
myDict["c"] = 2.3459999999;
// prepare the temp list
List<KeyValuePair<string, double>> list = new List<KeyValuePair<string, double>>(myDict);
// iterate through the list and then change the dictionary object
foreach (KeyValuePair<string, double> kvp in list)
myDict[kvp.Key] = Math.Round(kvp.Value, 3);
// print the output
foreach (var pair in myDict)
Console.WriteLine(pair.Key + " = " + pair.Value);
// uncomment if needed
// Console.ReadLine();
输出(在我的机器上):
a = 2.2 b = 77777.333 c = 2.346
注意:在性能方面,这个解决方案比目前发布的解决方案要好一些,因为值已经分配了键,不需要从字典对象中再次获取它.
【讨论】:
这可能是我将遵循的方法,但很想知道复制完整字典的开销。【参考方案4】:过了一段时间,但也许有人对此感兴趣:
yourDict = yourDict.ToDictionary(kv => kv.Key, kv => Math.Round(kv.Value, 3))
【讨论】:
非常易读的版本!添加错误检查时可能会变得有点混乱。【参考方案5】:我注意到(此时)用 modify 遍历 Dictionary 的最快方法是:
//Just a dumb class
class Test<T>
public T value;
public Test()
public Test(T v) value = v;
Dictionary<int, Test<object>> dic = new Dictionary<int, Test<object>>();
//Init dictionary
foreach (KeyValuePair<int, Test> pair in dic)
pair.Value.value = TheObject;//Modify
VS
List<int> keys = new List<int>(dic.Keys); //This is fast operation
foreach (int key in keys)
dic[key] = TheObject;
第一个大约需要 2.2 秒,第二个大约需要 4.5 秒(测试字典大小为 1000 并重复 10k 次,将字典大小更改为 10 不会改变比率)。获取密钥列表也没什么大不了的,字典 [key] 值获取只是慢 VS 内置迭代。此外,如果您想要更快的速度,请使用硬编码类型来哑(“测试”)类,我得到它大约 1.85 秒(硬编码为“对象”)。
编辑:
Anna 之前也发布过同样的解决方案:https://***.com/a/6515474/766304
【讨论】:
【参考方案6】:一种解决方案是预先将键放入列表(或另一个集合)中,并在更改字典时遍历它们:
Dictionary<string, double> dictionary = new Dictionary<string, double>();
// Populate it
List<string> keys = new List<string>(dictionary.Keys);
foreach (string key in keys)
dictionary[key] = Math.Round(dictionary[key], 3);
【讨论】:
【参考方案7】:虽然直接遍历字典是不可能的,因为你得到一个异常(就像 Ron 已经说过的那样),你不需要使用临时列表来解决问题。
而不是使用foreach
,而是使用for
循环来遍历字典并使用索引访问更改值:
Dictionary<string, double> myDict = new Dictionary<string,double>();
//...
for(int i = 0; i < myDict.Count; i++)
myDict[myDict.ElementAt(i).Key] = Math.Round(myDict.ElementAt(i).Value, 3);
【讨论】:
虽然这可行,但Enumerable.ElementAt()
- 这是您正在使用的扩展方法 - 是O(n)
operation for non-IList<>
s。
会明确说出 Eugene Beresovsky 的意思。这最终至少是 O(n^2)。所以这是一个非常糟糕的解决方案。【参考方案8】:
遍历字典中的键,而不是 KeyValuePairs。
Dictionary<string, double> myDict = new Dictionary<string, double>();
//...
foreach (string key in myDict.Keys)
myDict[key] = Math.Round(myDict[key], 3);
【讨论】:
令人惊讶的是它抛出了一个“Collection modified...”异常。 @Slauma 确实令人惊讶地,因为这种变化不是结构变化,因此实际上没有必要使用枚举器。以上是关于如何遍历字典并更改值?的主要内容,如果未能解决你的问题,请参考以下文章