Ricardo
Ricardo

Reputation: 468

Update a variable inside ForEach loop

I simply can't understand why this simple code is not working. My expected output is 10 and 15, but it is returning 2 and 3. That means that the update is not working.

List<int> numbers = new List<int>();

numbers.Add(2);
numbers.Add(3);

numbers.ForEach(n => n = n*5);

numbers.ForEach(n => Console.WriteLine(n));


Note: I've already searched a lot, but I could not understand this behavior.

How should I fix it?

Update: the same behavior for strings.

List<string> strings = new List<string>();
strings.Add("a");
strings.Add("b");

strings.ForEach(s => s = s + "--");

strings.ForEach(s => Console.WriteLine(s));

Upvotes: 2

Views: 10804

Answers (3)

Sleiman Jneidi
Sleiman Jneidi

Reputation: 23349

Because they are value types, rather than mutating the list you could create a modified one using Select

var newList= numbers.Select(n => n = n*5);

As imperative programmers, we love mutating things, which is not a brilliant idea!! The reason why it did not work for strings is that because by default C# passes a copy of the reference rather than the actual reference.

void Fn(string s)
{
  s = "not being changed";
}   

Main()
{  
var hello = "hello";
Fn(hello);
Console.WriteLine (hello); // prints hello again!!
}

However, if you want to change the reference you have to use the ref keyword.

void Fn(ref string s)
{
  s = "Unfortunately, changed!";
}   

Main()
{  
var hello = "hello";
Fn(ref hello);
Console.WriteLine (hello); // Unfortunately, changed!!!
}

I think that changing parameters' values is a terrible idea and you shouldn't be doing that, you should return a new string that contains the new modifications.

Upvotes: 4

Selman Gen&#231;
Selman Gen&#231;

Reputation: 101732

n is a copy of your current value in the list not a reference to your value.If you want to manipulate the values in your list then use a for loop

for(int i = 0; i<numbers.Count; i++)
    numbers[i] *= 5;

More detailed explanation:

With a normal foreach loop your code doesn't even compile:

foreach(var n in numbers)
      n = n * 5; // Readonly local variable cannot be used as an assignment target

Remember that List<T>.ForEach loop is not the same as foreach but it is just a method that takes a Action<int> delegate as argument and performs the specified action on the each element in your list.So it performs something like this (taken from the source code):

 public void ForEach(Action<T> action) 
 {
     // removed unnecessary parts for brevity
     for(int i = 0 ; i < _size; i++) 
     {
         action(_items[i]);
     }
 } 

As you can see here the _item[i] is passed to the action and since int is a value types the copy of your value is passed rather than a reference.And that's why your values didn't change.

For strings: Apart from the fact that strings are immutable, assigning a new reference to a reference type doesn't change the object that holds the same reference.For example consider this:

static void Update(string s)
{
     s = "bar";
}

string f = "foo";
Update(f);
Console.WriteLine(f); // foo

Assigning a new reference to s doesn't change the f, f stil holds the old reference and s is pointing to a new location in memory.This is not because s is a copy,it's not.If you change a property of s (with strings you can't do that but try with another reference type), it would update the property of f as well.It works in this way because s and f are two different strings that points to the same location in memory.So s is not bound to f.You can think they were declared like this:

string f = "foo";
string s = f;
s  = "bar";

The only exception is when you pass f as a ref argument then the assignment will change the f as well:

static void Update(ref string s)
{
     s = "bar";
}

string f = "foo";
Update(ref f);
Console.WriteLine(f); // bar

Upvotes: 9

Dan Hunex
Dan Hunex

Reputation: 5318

The reason is because the parameter to the ForEach are passed by value and not by reference. However, if you do pass a reference type, it must work as expected as shown below

class Program
    {
        static void Main(string[] args)
        {
            List<Frog> numbers = new List<Frog>();

            numbers.Add(new Frog { name = "balcha" });
            numbers.Add(new Frog { name = "Tibara" });
            numbers.ForEach(n => n.name = "Bontu");
            numbers.ForEach(n => Console.WriteLine(n.name));
            Console.ReadLine();
        }

        class Frog
        {
            public string name { get; set; }
        }
    }

Output:

 Bontu
 Bontu

Upvotes: 3

Related Questions