tags:

views:

572

answers:

4

I have the following code:

newsplit.ToList().ForEach(x => x = "WW");

I would expect that all elements in the list are now "WW" but they are still the original value. How come? What do I have to do different?

+12  A: 

Assuming that newsplit is an IEnumerable<string>, you want:

newsplit = newsplit.Select(x => "WW");

The code that you currently have is equivalent to the following:

foreach(string x in newsplit.ToList()) {
    AssignmentAction(x);
}

...

public static void AssignmentAction(string x) {
    x = "WW";
}

This method won't modify x because of the pass-by-value semantics of C# and the immutability of strings.

Jason
Is a string array or in my case a List<byte[]> IEnumerable as I dont think it is?
Jon
@Jon: arrays and List<T> all support the IEnumerable interface
Jeff Yates
I don't think a Select() is what Jon wants, it sounds like he wants to modify the IEnumerable itself. Select will just give him the items that already are "WW"
AgileJon
@AgileJon: It sounds like you are confusing IEnumerable<T>.Where and IEnumerable<T>.Select.
Jason
@Downvoters: Two downvotes? Is this wrong? Is this harmful? What am I overlooking?
Jason
@Jason: you are right, I was confusing Select with Where. Downvote removed.
AgileJon
newSplit is a list of items, in this example it is a string but I also have a List<byte[]>. I want to change each item in the List. However List<string> test = newsplit.Select(x => x = "WW"); doesn't work. Does it need to be List<string> test = newSplit.ToList().Select(x => x = "WW"); ??
Jon
@Jon: If you want to create a new List<string> with the altered values you could say List<string> test = newsplit.Select(x => "WW").ToList().
Jason
What if you already have a list and want to change its values, surely calling Select.ToList() is a bit odd as its already a list?
Jon
A: 

Isn't it because the LINQ expression is creating Anonymous Types, and these are readonly? They can't be assigned to. Also, in a standard for each loop you cannot assign to a collection that is being enumerated. Try:

    foreach (var item in newsplit)
{
 item = "this won't work";
}
Dan Diplo
There is no anonymous type created in the given ForEach expression. The parameter is a delegate, which is applied to each element in the iteration; and of course, won't work for the reason you give.
Steve Guidi
You are right, of course - not sure what I was thinking! But the second part, about collections being immutable when iterated, is worth noting. Thanks.
Dan Diplo
+1  A: 

The ForEach will allow you to manipulate the elements of the IEnumerable, but not change the reference of the element.

ie, this would set a Foo property of each element in the IEnumerable to the string "WW":

newsplit.ToList().ForEach(x => x.Foo = "WW");

However, you won't be able to modify the values inside the IEnumerable itself.

AgileJon
+2  A: 

Other answers have explained why your current code doesn't work. Here's an extension method which would fix it:

// Must be in a static non-nested class
public static void ModifyEach<T>(this IList<T> source,
                                 Func<T,T> projection)
{
    for (int i = 0; i < source.Count; i++)
    {
        source[i] = projection(source[i]);
    }
}

Then use like this:

newsplit.ModifyEach(x => "WW");

That will work with any implementation of IList<T> such as arrays and List<T>. If you need it to work with an arbitrary IEnumerable<T> then you've got a problem, as the sequence itself may not be mutable.

Using Select() is a more functional approach of course, but sometimes mutating an existing collection is worth doing...

Jon Skeet
@Jon: I think you meant newsplit.ToList().ModifyEach(x => "WW") in your second code block?
Jason
@Jason: Nearly... but if you call ToList() but don't keep a reference to that new list, the modifications won't be preserved. In particular it would *not* modify the original collection. Have edited answer for intended use.
Jon Skeet
@Jon: Agree. Thanks for clarifying.
Jason