# C# 避免在lamda內使用iteration variable 我們在使用lambda或是匿名函式時,需要避免再迴圈內使用迭代的變數。而遇到不如預期的結果,比如**每次執行的值都一樣、觸發OutOfRangeException**等等之類的問題(端看實作的情境) ```csharp for (int i = 0; i < length; i++) // i 表示 迭代的變數 ``` ## 問題情境 在開發遊戲時,有時會在迴圈中使用lamda 或是匿名函式來達成一些功能 比如說以下情境是,我撰寫一個迴圈,每次迴圈會透過Corutine等待一秒鐘後,印出當前Index ```csharp public class TestLambda : MonoBehaviour { private void Start() { int length = 3; for (int i = 0; i < length; i++) { StartCoroutine( WaitOutput(() => { Debug.Log(i); } )); } } public IEnumerator WaitOutput(Action OnComplete) { yield return new WaitForSeconds(1.0f); OnComplete?.Invoke(); } } ``` ### 實際預期應該是 ```csharp 1 2 3 ``` ### 實際執行結果 ```csharp 3 3 3 ``` ## 發生原因 經過參考資料後,經過反組譯來查看IL的程式碼 ```csharp private void Start() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown int num = 10; <>c__DisplayClass0_0 CS$<>8__locals0 = new <>c__DisplayClass0_0(); CS$<>8__locals0.i = 0; while (CS$<>8__locals0.i < num) { ((MonoBehaviour)this).StartCoroutine(WaitOutput((Action)delegate { Debug.Log((object)CS$<>8__locals0.i); )); CS$<>8__locals0.i++; } } [IteratorStateMachine(/*Could not decode attribute arguments.*/)] public IEnumerator WaitOutput(Action OnComplete) { yield return new WaitForSeconds(1f); if (OnComplete != null) { OnComplete.Invoke(); } } ``` 觀察印出Index的程式碼 `Debug.Log((object)CS$<>8__locals0.i);` 印出的Index則是儲存於`CS$<>8__locals0` 物件中的變數`i` 物件`CS$<>8__locals0` 則是起初宣告於迴圈之外 `<>c__DisplayClass0_0 CS$<>8__locals0 = new <>c__DisplayClass0_0();` 每次執行完迴圈便會執行 `CS$<>8__locals0.i++` 將index的值+1 故最終執行完迴圈後 所有的lamda內指向的物件`CS$<>8__locals0` 都是**同一個** 因此會得到相同的結果 ## 改善的方式 我們在迴圈內將**新增一個變數儲存迭代變數的數值** ```csharp for (int i = 0; i < length; i++) // 將i的數值儲存於localVariable int localVariable = i; ``` **改善後的程式碼** ```csharp public class TestLambda : MonoBehaviour { private void Start() { int length = 3; for (int i = 0; i < length; i++) { int localVariable = i; StartCoroutine( WaitOutput(() => { Debug.Log(localVariable); } )); } } public IEnumerator WaitOutput(Action OnComplete) { yield return new WaitForSeconds(1.0f); OnComplete?.Invoke(); } } ``` **執行結果** ```csharp 1 2 3 ``` **反組譯來查看IL的程式碼** ```csharp private void Start() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown int num = 10; for (int i = 0; i < num; i++) { <>c__DisplayClass0_0 CS$<>8__locals0 = new <>c__DisplayClass0_0(); CS$<>8__locals0.local = i; ((MonoBehaviour)this).StartCoroutine(WaitOutput((Action)delegate { Debug.Log((object)CS$<>8__locals0.local); })); } } [IteratorStateMachine(/*Could not decode attribute arguments.*/)] public IEnumerator WaitOutput(Action OnComplete) { yield return new WaitForSeconds(1f); if (OnComplete != null) { OnComplete.Invoke(); } } ``` 原先的`CS$<>8__locals0` 被宣告於迴圈之內 故所有的lamda內指向的物件,皆為**不同的物件** 所以lamda 執行的結果,才會與預期結果相符 若有任何錯誤的內容,歡迎大家指正~ 參考資料: - **[Why is it bad to use an iteration variable in a lambda expression](https://stackoverflow.com/questions/227820/why-is-it-bad-to-use-an-iteration-variable-in-a-lambda-expression)** - [**https://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach**](https://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach) - [https://blog.csdn.net/p15097962069/article/details/105200833](https://blog.csdn.net/p15097962069/article/details/105200833)