【问题标题】: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&lt;T&gt; { public T boxed; },然后使用Dictionary&lt;string, Box&lt;double&gt;&gt;,然后您可以在 foreach 中“修改”类似:kvp.Value.boxed = 123.456;。并不是说这是“最好”的方法,但它有它的用途。
  • 这能回答你的问题吗? Editing dictionary values in a foreach loop

标签: .net dictionary c#-3.0


【解决方案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 中测试了您的示例,它抛出“集合已修改异常”。请参阅:stackoverflow.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 参见stackoverflow.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://stackoverflow.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&lt;&gt;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 确实令人惊讶地,因为这种变化不是结构变化,因此实际上没有必要使用枚举器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 2022-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-19
      相关资源
      最近更新 更多