2011-12-13 74 views
5

當測試一個簡單的ForEach擴展方法時,遇到了對我來說意想不到的結果。行動/委託可以改變它的參數值嗎?

ForEach方法

public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) 
{ 
    if (action == null) throw new ArgumentNullException("action"); 

    foreach (T element in list) 
    { 
     action(element); 
    } 
} 

Test方法

[TestMethod] 
public void BasicForEachTest() 
{ 
    int[] numbers = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 

    numbers.ForEach(num => 
    { 
     num = 0; 
    }); 

    Assert.AreEqual(0, numbers.Sum()); 
} 

爲什麼會numbers.Sum()等於55,而不是0?

回答

5

num是您正在迭代的當前元素的值的副本。所以你只是更改副本。

你要做的基本上是這樣的:

foreach(int num in numbers) 
{ 
    num = 0; 
} 

當然你不要指望這改變數組的內容?

編輯:你需要的是這樣的:

for (int i in numbers.Length) 
{ 
    numbers[i] = 0; 
} 

在特定情況下,你可以在你的ForEach擴展方法維護索引和傳遞的第二個參數的動作,然後使用它像這樣:

numbers.ForEachWithIndex((num, index) => numbers[index] = 0); 

但是一般:創建LINQ的風格的擴展方法,其修改它們應用到回收的不良作風(IMO)。如果你編寫的擴展方法不能應用於IEnumerable<T>,那麼如果你真的需要它(特別是當你想要修改集合的時候),你應該真的認真思考它。你沒有太多的收穫,但有很多東西要放鬆(比如意想不到的副作用)。我確信有例外,但我堅持這一規則,它對我很好。

+0

@ 249076:是的,這將工作。 – 2011-12-13 18:55:05

0

因爲intvalue type並作爲值參數傳遞給您的擴展方法。因此,將numbers的副本傳遞給您的ForEach方法。存儲在BasicForEachTest方法中初始化的numbers數組中的值不會被修改。

請檢查Jon Skeet的article以瞭解更多關於值類型和值參數的信息。

1

因爲num是副本。 這是因爲如果你這樣做:

int i = numbers[0]; 
i = 0; 

你不會想到要更換號碼[0],你會嗎?

0

我不是聲稱這個答案中的代碼是有用的,但(它的工作原理和)我認爲它說明了你需要什麼使你的方法工作。論點必須標記爲ref。首創置業不具有委託類型與ref,所以只寫你自己的(沒有任何類中):

public delegate void MyActionRef<T>(ref T arg); 

就這樣,你的方法就變成了:

public static void ForEach2<T>(this T[] list, MyActionRef<T> actionRef) 
{ 
    if (actionRef == null) 
    throw new ArgumentNullException("actionRef"); 

    for (int idx = 0; idx < list.Length; idx++) 
    { 
    actionRef(ref list[idx]); 
    } 
} 

現在,記得使用ref關鍵字在您的測試方法:

numbers.ForEach2((ref int num) => 
{ 
    num = 0; 
}); 

這工作,因爲這是確定傳遞一個數組項爲ByRef(ref)。

如果你想擴展IList<>相反,你要做的:

public static void ForEach3<T>(this IList<T> list, MyActionRef<T> actionRef) 
{ 
    if (actionRef == null) 
    throw new ArgumentNullException("actionRef"); 

    for (int idx = 0; idx < list.Count; idx++) 
    { 
    var temp = list[idx]; 
    actionRef(ref temp); 
    list[idx] = temp; 
    } 
} 

希望這有助於你的理解。

注意:我不得不使用for循環。在C#中,在foreach (var x in Yyyy) { /* ... */ }中,不允許將其分配給x(其中包括在循環體內傳遞x ByRef(與refout))。

相關問題