Here's a gotcha that's obvious once you see it, but was the cause of a bug that lived for several months undetected in code I came across today.
Do you think this code will print modified elements or the original values?
var stringList = new List<string>(new[] { "a", "b", "c" });
// What effect does this have?
stringList.ForEach(element => element = element + " modified");
// Let's see!
stringList.ForEach(element => Console.WriteLine(element));
Here's the output. It's not what we wanted, is it!?
a
b
c
ForEach's parameter is an Action<T>. Each element in the list is passed to the Action, which can do something to the element but cannot replace it with a new value.
So how about this?
class MyType
{
public string MyString { get; set; }
public MyType(string s)
{
MyString = s;
}
}
class Program
{
static void Main(string[] args)
{
var myList = new List<MyType>(new[] { new MyType("a"), new MyType("b"), new MyType("c") });
// What effect does this have?
myList.ForEach(element => element.MyString = element.MyString + " modified");
// Let's see!
myList.ForEach(element => Console.WriteLine(element.MyString));
}
}
This time, we modified elements rather than attempting to replace them and we get what we'd expect:
a modified
b modified
c modified
What if we change MyType from a class to a struct, keeping the rest of the program the same?
struct MyType
{
public string MyString { get; set; }
public MyType(string s) : this()
{
MyString = s;
}
}
class Program
{
static void Main(string[] args)
{
var myList = new List<MyType>(new[] { new MyType("a"), new MyType("b"), new MyType("c") });
// What effect does this have?
myList.ForEach(element => element.MyString = element.MyString + " modified");
// Let's see!
myList.ForEach(element => Console.WriteLine(element.MyString));
}
}
Because a struct is a value type, a copy of each element, not a reference, gets passed to the Action and the modification is done on the copy. When the Action returns, the original is unchanged. Here's what prints.
a
b
c
A technique that will work for all of the above cases must involve making a new list. Here's a way that uses LINQ's Aggregate method (like my trick for comma-separating a List). I like this because it embeds the working list in the Aggregate statement and does not expose it to the surrounding code.
struct MyType
{
public string MyString { get; set; }
public MyType(string s) : this()
{
MyString = s;
}
}
class Program
{
static void Main(string[] args)
{
var myList = new List<MyType>(new[] { new MyType("a"), new MyType("b"), new MyType("c") });
myList = myList.Aggregate(
new List<MyType>(),
(newList, element) =>
{
element.MyString = element.MyString + " modified";
newList.Add(element);
return newList;
});
myList.ForEach(element => Console.WriteLine(element.MyString));
}
}
Voila!
a modified
b modified
c modified