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).
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("");
See more on this question at Stackoverflow