C# 遍歷Dictionary並修改其中的Value

forbetter223發表於2018-11-27

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”後斷掉。
---------------------------------------------------------------------------------------------------------------------------------------------------
為什麼For each迴圈不能改變集合中各項的值
 首先,我們知道要使用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`2.
4         //
5         // Returns:
6         //     A System.Collections.Generic.Dictionary`2.Enumerator structure for the System.Collections.Generic.Dictionary`2.
7         public Enumerator GetEnumerator();

其實最主要的是Enumerator介面,我們看看它的程式碼:

 1 namespace System.Collections
 2 {
 3     //
 4     // Summary:
 5     //     Supports a simple iteration over a non-generic collection.
 6     [ComVisible(true)]
 7     [Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
 8     public interface IEnumerator
 9     {
10         //
11         // Summary:
12         //     Gets the element in the collection at the current position of the enumerator.
13         //
14         // Returns:
15         //     The element in the collection at the current position of the enumerator.
16         object Current { get; }
17  
18         //
19         // Summary:
20         //     Advances the enumerator to the next element of the collection.
21         //
22         // Returns:
23         //     true if the enumerator was successfully advanced to the next element; false if
24         //     the enumerator has passed the end of the collection.
25         //
26         // Exceptions:
27         //   T:System.InvalidOperationException:
28         //     The collection was modified after the enumerator was created.
29         bool MoveNext();
30         //
31         // Summary:
32         //     Sets the enumerator to its initial position, which is before the first element
33         //     in the collection.
34         //
35         // Exceptions:
36         //   T:System.InvalidOperationException:
37         //     The collection was modified after the enumerator was created.
38         void Reset();
39     }
40 }

大家看到了,object Current { 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就可以改變了。大家可以試試。

 

 

相關文章