A Limitation of Lambda Expressions and Overloaded Extension Methods
Tamir hates lambdas. He was having a problem with one of his lambda expressions and twittered about it. Around that time I opened my twitter account (yes, Yosi finally convinced me) and offered my help.
He wanted to have a single extension method that could iterate over a collection and either change or keep the values it got. Kind of like this:
items.ForEach(item => item.SubItems.ForEach(subItem => subItem = newValue));
Where ForEach was defined as:
static void ForEach<T>(this IList<T> collection, Action<T> action)
{
for (int i = 0; i < collection.Count; i++)
{
action(collection[i]);
}
}
This did not work simply because subItem is a local variable - a copy of either the reference (when using a Reference Type) or of the value itself (when using a Value Type) - the lambda expression was (very loosely) translated to the following:
void Foo(SubItem subItem)
{
subItem = newValue;
}
What I then tried to do was to replace Action<T> with a delegate with a ref parameter, so that the local variable will change. It turns out lambda expressions can not contain ref parameters.
On to the next solution - overloading the ForEach extension method:
static void ForEach<T>(this IList<T> collection, Action<T> action)
{
for (int i = 0; i < collection.Count; i++)
{
action(collection[i]);
}
}
static void ForEach<T>(this IList<T> collection, Func<T, T> action)
{
for (int i = 0; i < collection.Count; i++)
{
collection[i] = action(collection[i]);
}
}
This way, one can now write the following:
items.ForEach(item => item.SubItems.ForEach(subItem => newValue));
The first call (outer ForEach) will use the overload with Action<T> and the second (inner ForEach) will use the Func<T, T> overload and end up changing the current subItem. The C# compiler does a nice job finding the best overload to call.