Introduction
In C#, you have various possibilities to iterate over a list like for
loop, foreach
loop or with LINQ. When you use a List(T)
type you have even one more, the ForEach
method. But this method doesn't always show the same behaviour as a normal foreach
loop.
Using the Code
The ForEach
method of the List<T>
(not IList<T>
) executes an operation for every object which is stored in the list. Normally it contains code to either read or modify every object which is in the list or to do something with list itself for every object.
Modify the Object Itself
The following sample with a ForEach
method loops over all stored Points
in the collection. It subtracts 10 from the x coordinate of the point. At the end, the Points
will be printed to the console.
List<Point> points = new List<Point>(){ new Point(14, 10), new Point(19, 10) };
items.ForEach(point => point.X = point.X - 10);
foreach (Point point in points)
{
Console.WriteLine(point);
}
The output in the console is in this case {X=14, Y=10} and {X=19, Y=10}. I expected that X is 4 and 9, so what's wrong? If you put the same logic into a normal foreach
statement, the compiler throws the following error: "Cannot modify members of 'point' because it is a 'foreach
iteration variable'". If we define our own type, the code does what it should do!
public class MyPoint
{
public MyPoint(int x, int y){ X = x; Y = y; }
public int X{ get; set; }
public int Y{ get; set; }
}
List<MyPoint> points = new List<MyPoint>(){ new MyPoint(14, 10), new MyPoint(19, 10) };
items.ForEach(point => point.X = point.X - 10);
foreach (MyPoint point in points)
{
Console.WriteLine(point);
}
The difference is, that Point
is a value type, a struct, and MyPoint
is a reference type. So in the case where Point
is used, a copy of the object is passed to the method, not the object itself. So if the action, which is passed into the ForEach
method, changes the copy, but it won't affect the original object.
Modify the Collection
When you use a normal foreach
statement, you can't add or remove items while iterating over the collection. But with List.ForEach
you can, so the following code can be executed without any errors. Which result do you expect?
public class Integer
{
public int Value { get; set; }
public Integer(int value) { Value = value; }
}
public void Sample()
{
List<Integer> items = new List<Integer>()
{
new Integer(14),
new Integer(0),
new Integer(19)
};
items.ForEach(item =>
{
if (item.Value == 0)
{
items.Remove(item);
}
item.Value = item.Value - 10;
});
foreach (Integer item in items)
{
Console.WriteLine(item.Value);
}
}
The result which is shown in the console is 4 and 19. So this is a good example of not all what you can do, you also should do! The result should be 4 and 9! It seems that internally a for
loop is used, which iterates backward over the collection.
Points of Interest
So List<T>.ForEach
allows several things which are blocked in a foreach
loop. These things aren't allowed for a good reason. So if you want to store objects of value types, like int
, long
, double
, bool
or even string
, in a generic List
, you shouldn't use the ForEach
method if you want to avoid problems. A good solution is to use a for
loop and access the data over the indexer of the collection. Also removing items in the ForEach
method is a thing which should be avoided when it is possible.