C# 遍历Dictionary并修改其中的Value

C#的Dictionary类型的值,知道key后,value可以修改吗?答案是肯定能修改的。我在遍历的过程中可以修改Value吗?答案是也是肯定能修改的,但是不能用For each循环。否则会报以下的Exception.

System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'

之所以会报Exception是For each本身的问题,和Dictionary没关系。For each循环不能改变集合中各项的值,如果需要迭代并改变集合项中的值,请用For循环。

大家来看下例子:

 1             // defined the Dictionary variable
 2             Dictionary<int, string> td = new Dictionary<int, string>();
 3             td.Add(1, "str1");
 4             td.Add(2, "str2");
 5             td.Add(3, "str3");
 6             td.Add(4, "str4");
 7             // test for
 8             TestForDictionary(td);
 9             // test for each
10             TestForEachDictionary(td);
TestForDictionary Code
1         static void TestForDictionary(Dictionary<int, string> paramTd)
2         {
3             
4             for (int i = 1;i<= paramTd.Keys.Count;i++)
5             {
6                 paramTd[i] = "string" + i;
7                 Console.WriteLine(paramTd[i]);
8             }
9         }
TestForDictionary的执行结果
string1
string2
string3
string4

TestForEachDictionary Code
 1         static void TestForEachDictionary(Dictionary<int, string> paramTd)
 2         {
 3             int forEachCnt = 1;
 4             foreach (KeyValuePair<int,string> item in paramTd)//System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
 5             {
 6                 paramTd[item.Key] = "forEach" + forEachCnt;
 7                 Console.WriteLine(paramTd[item.Key]);
 8                 forEachCnt += 1;
 9             }
10         }
TestForEachDictionary里的For each会在循环第二次的时候报错,也就是说它会在窗口中打印出“forEach1”后断掉。
---------------------------------------------------------------------------------------------------------------------------------------------------
为什么foreach循环不能改变Dictionary中Key对应的Value?
 首先,我们知道要使用Foreach in语句需要满足下列条件:
1.迭代集合实现了System.Collections.IEnumerable或者System.Collections.Generic.IEnumerable<T>接口;
2.有public Enumerator GetEnumerator();方法
3.GetEnumerator的返回类型是Enumerator,那就必须有Current属性和MoveNext方法
其实1,2,3是一个东西,关联性很强。
我迭代的集合是Dictionary,我把Dictionary的代码片段贴出来:
1     [ComVisible(false)]
2     [DebuggerDisplay("Count = {Count}")]
3     [DebuggerTypeProxy(typeof(Generic.Mscorlib_DictionaryDebugView<,>))]
4     [DefaultMember("Item")]
5     public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, IDictionary, ICollection, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, ISerializable, IDeserializationCallback
6     {
7     ...
8     }

Dictionary也有无参公共方法GetEnumerator

1 //
2         // Summary:
3         //     Returns an enumerator that iterates through the System.Collections.Generic.Dictionary<TKey,TValue>.
4         //
5         // Returns:
6         //     A System.Collections.Generic.Dictionary<TKey,TValue>.Enumerator structure
7         //     for the System.Collections.Generic.Dictionary<TKey,TValue>.
8         public Dictionary<TKey, TValue>.Enumerator GetEnumerator();

我们再看看Dictionary<TKey, TValue>.Enumerator的代码

 1         // Summary:
 2         //     Enumerates the elements of a System.Collections.Generic.Dictionary<TKey,TValue>.
 3         [Serializable]
 4         public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, IDictionaryEnumerator, IEnumerator
 5         {
 6 
 7             // Summary:
 8             //     Gets the element at the current position of the enumerator.
 9             //
10             // Returns:
11             //     The element in the System.Collections.Generic.Dictionary<TKey,TValue> at
12             //     the current position of the enumerator.
13             public KeyValuePair<TKey, TValue> Current { get; }
14 
15             // Summary:
16             //     Releases all resources used by the System.Collections.Generic.Dictionary<TKey,TValue>.Enumerator.
17             public void Dispose();
18             //
19             // Summary:
20             //     Advances the enumerator to the next element of the System.Collections.Generic.Dictionary<TKey,TValue>.
21             //
22             // Returns:
23             //     true if the enumerator was successfully advanced to the next element; false
24             //     if the enumerator has passed the end of the collection.
25             //
26             // Exceptions:
27             //   System.InvalidOperationException:
28             //     The collection was modified after the enumerator was created.
29             public bool MoveNext();
30         }

让我们看看,我们能否修改KeyValuePair的值

 1     public struct KeyValuePair<TKey, TValue>
 2     {
 3         //
 4         // Summary:
 5         //     Initializes a new instance of the System.Collections.Generic.KeyValuePair<TKey,TValue>
 6         //     structure with the specified key and value.
 7         //
 8         // Parameters:
 9         //   key:
10         //     The object defined in each key/value pair.
11         //
12         //   value:
13         //     The definition associated with key.
14         public KeyValuePair(TKey key, TValue value);
15 
16         // Summary:
17         //     Gets the key in the key/value pair.
18         //
19         // Returns:
20         //     A TKey that is the key of the System.Collections.Generic.KeyValuePair<TKey,TValue>.
21         public TKey Key { get; }
22         //
23         // Summary:
24         //     Gets the value in the key/value pair.
25         //
26         // Returns:
27         //     A TValue that is the value of the System.Collections.Generic.KeyValuePair<TKey,TValue>.
28         public TValue Value { get; }
29 
30         // Summary:
31         //     Returns a string representation of the System.Collections.Generic.KeyValuePair<TKey,TValue>,
32         //     using the string representations of the key and value.
33         //
34         // Returns:
35         //     A string representation of the System.Collections.Generic.KeyValuePair<TKey,TValue>,
36         //     which includes the string representations of the key and value.
37         public override string ToString();
38     }

大家看到了,TValue Value { get; }是我们需要修改的项,只有get方法,所以它是只读的。


上面是从代码方面解释foreach迭代时不可改变集合项,还有一个原因是是你改变值类型的值时,它对应的栈地址也就相应的改变了,这个大家都知道,我就不多说了。那么问题来了,如果我不改变变量的地址,是不是我就可以用Foreach改变集合项呢?答案是肯定的。可以看下边MSDN的代码:

 1 static void ModifyDictionaryValueForEach()
 2  {
 3      Span<int> storage = stackalloc int[10];
 4      int num = 0;
 5      foreach (ref int item in storage)
 6      {
 7          item = num++;
 8      }
 9  
10      foreach (ref readonly var item in storage)
11      {
12          Console.Write($"{item} ");
13      }
14      // Output:
15      // 0 1 2 3 4 5 6 7 8 9
16  }

上边的代码必须是在C#版本是7.3下运行.Span<T>是.net Core2.1里的类型。然后使用ref就可以改变了。大家可以试试。

 

//        // Summary:        //     Returns an enumerator that iterates through the System.Collections.Generic.Dictionary<TKey,TValue>.        //        // Returns:        //     A System.Collections.Generic.Dictionary<TKey,TValue>.Enumerator structure        //     for the System.Collections.Generic.Dictionary<TKey,TValue>.        public Dictionary<TKey, TValue>.Enumerator GetEnumerator();

原文地址:https://www.cnblogs.com/forbetter223/p/10026113.html