2011-12-13 87 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))。

相关问题