Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can an Action/delegate change it's arguments value?

I ran into what was to me an unexpected result when testing a simple ForEach extension method.

ForEach method

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 method

[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());
}

Why would numbers.Sum() be equal to 55 and not 0?

like image 252
249076 Avatar asked Dec 13 '11 18:12

249076


People also ask

What is the difference between action and delegate?

An action is a delegate whose signature has no parameters and has no return value. You can't use Action without using a delegate at the same time, since Action is a specific delegate type.

Is action a delegate?

Action is a delegate type defined in the System namespace. An Action type delegate is the same as Func delegate except that the Action delegate doesn't return a value. In other words, an Action delegate can be used with a method that has a void return type.

Are Func and Action delegates?

Func is a delegate that points to a method that accepts one or more arguments and returns a value. Action is a delegate that points to a method which in turn accepts one or more arguments but returns no value. In other words, you should use Action when your delegate points to a method that returns void.


1 Answers

num is the copy of the value of the current element you are iterating over. So you are just changing the copy.

What you do is basically this:

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

Surely you do not expect this to change the content of the array?

Edit: What you want is this:

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

In your specific case you could maintain an index in your ForEach extension method and pass that as second argument to the action and then use it like this:

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

However in general: Creating Linq style extension methods which modify the collection they are applied to are bad style (IMO). If you write an extension method which cannot be applied to an IEnumerable<T> you should really think hard about it if you really need it (especially when you write with the intention of modifying the collection). You have not much to gain but much to loose (like unexpected side effects). I'm sure there are exceptions but I stick to that rule and it has served me well.

like image 104
ChrisWue Avatar answered Sep 30 '22 02:09

ChrisWue