Attempting to assign to a C# 'foreach iteration variable' generates a compile time error. Doing so in a Linq [IEnumerable].ForEach(...) does not. Why?

I noticed what I believe to be strange behaviour in C# and Linq today when trying to make a class a bit tidier and remove a for loop, used to initialise an array of strings.

My original code:

    for(int i = 0; i < tagCount; i++)
    {
        myObj.tags[i] = string.Empty;
    }

It is known that values cannot be assigned to the iteration variable in a C# foreach loop. The following, for instance, generates a compile-time error (where myObj is an object which contains an array of strings called 'tags'):

    foreach(string s in myObj.tags)
    {
        s = string.Empty;
    }

    Gives: "Cannot assign to 's' because it is a 'foreach iteration variable'"

But doing the following compiles correctly:

    myObj.tags.ToList().ForEach(s => s = string.Empty);

My question is why this syntax compiles without any issue?


NOTE: Strange Behaviour - Possible Bug? - Below

As a follow-up, I think it certainly feel it worth noting that, whilst the Linq method compiles, during run-time, the effect of the command s=string.Empty is not seen, nor is a run-time error generated. string.Empty can be replaced with any value, and inspection of any of the indices in the myObj.tags array will show a null value. Breaking and stepping into the ...ForEach(s=>s=string.Empty) shows the line being executed as expected, but no change in the value of s.

This struck me as strange behaviour mostly because no error had been generated either - it seemingly just does nothing. Is this by design? An oversight?

Thank you all for any input.


(P.S. I am aware of the performance hit of ToList().ForEach(). I just wanted to see if I could remove that for-loop. I am now more interested in the reason behind the behaviour exhibited during compilation and run-time).

Jon Skeet
people
quotationmark

Yes, it does nothing - but there are lots of cases where that happens in C#. It's not clear where you'd expect it to be an error, or what you'd expect it to achieve. Basically your lambda expression is a shorthand for writing a method like this:

static void Foo(string s)
{
    s = string.Empty;
}

That's not a useful method, but it's a perfectly valid one. Calling it for each value within the list doesn't make it any more useful.

Assigning back to a value parameter (rather than a ref or out parameter) and not reading from the parameter again is basically pointless, but valid.

In terms of removing the loop - one thing you could do is write an extension method:

public static void ReplaceAll<T>(this IList<T> list, T value)
{
    // TODO: Validation
    for (int i = 0; i < list.Count; i++)
    {
        list[i] = value;
    }
}

Then you could call:

myObj.tags.ReplaceAll("");

people

See more on this question at Stackoverflow