List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
To me, the difference is purely cosmetic, but are there any subtle reasons why one might be preferred over the other?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
To me, the difference is purely cosmetic, but are there any subtle reasons why one might be preferred over the other?
Looking at the compiled code through ILSpy, there actually is a difference in the two references. For a simplistic program like this:
namespace ScratchLambda
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
internal class Program
{
private static void Main(string[] args)
{
var list = Enumerable.Range(1, 10).ToList();
ExplicitLambda(list);
ImplicitLambda(list);
}
private static void ImplicitLambda(List<int> list)
{
list.ForEach(Console.WriteLine);
}
private static void ExplicitLambda(List<int> list)
{
list.ForEach(s => Console.WriteLine(s));
}
}
}
ILSpy decompiles it as:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda
{
internal class Program
{
private static void Main(string[] args)
{
List<int> list = Enumerable.Range(1, 10).ToList<int>();
Program.ExplicitLambda(list);
Program.ImplicitLambda(list);
}
private static void ImplicitLambda(List<int> list)
{
list.ForEach(new Action<int>(Console.WriteLine));
}
private static void ExplicitLambda(List<int> list)
{
list.ForEach(delegate(int s)
{
Console.WriteLine(s);
}
);
}
}
}
If you look at the IL call stack for both, the Explicit implementation has a lot more calls (and creates a generated method):
.method private hidebysig static
void ExplicitLambda (
class [mscorlib]System.Collections.Generic.List`1<int32> list
) cil managed
{
// Method begins at RVA 0x2093
// Code size 36 (0x24)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0006: brtrue.s IL_0019
IL_0008: ldnull
IL_0009: ldftn void ScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
IL_000f: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
IL_0014: stsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0019: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_001e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
IL_0023: ret
} // end of method Program::ExplicitLambda
.method private hidebysig static
void '<ExplicitLambda>b__0' (
int32 s
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x208b
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call void [mscorlib]System.Console::WriteLine(int32)
IL_0006: ret
} // end of method Program::'<ExplicitLambda>b__0'
while the Implicit implementation is more concise:
.method private hidebysig static
void ImplicitLambda (
class [mscorlib]System.Collections.Generic.List`1<int32> list
) cil managed
{
// Method begins at RVA 0x2077
// Code size 19 (0x13)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldnull
IL_0002: ldftn void [mscorlib]System.Console::WriteLine(int32)
IL_0008: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
IL_000d: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
IL_0012: ret
} // end of method Program::ImplicitLambda
I'd prefer the lambda syntax in general. When you see that, then it tells you what the type is. When you see Console.WriteLine
, you'd have to ask the IDE what type it is. Of course, in this trivial example, it's obvious, but in the general case, it might not be so much.
with the two examples you gave they differ in that when you say
List.ForEach(Console.WriteLine)
you are actually telling the ForEach Loop to use the method WriteLine
List.ForEach(s => Console.WriteLine(s));
is actually defining a method that the foreach will call and then you are telling it what to handle there.
so for simple one liners if your method you are going to call carries the same signature as the method that gets called already I would prefer not to define the lambda, I think its a little more readable.
for methods with incompatible lambdas are definitely a good way to go, assuming they aren't to overly complicated.
There is a very strong reason to prefer the first line.
Every delegate has a Target
property, which allows delegates to refer to instance methods, even after the instance has gone out of scope.
public class A {
public int Data;
public void WriteData() {
Console.WriteLine(this.Data);
}
}
var a1 = new A() {Data=4};
Action action = a1.WriteData;
a1 = null;
We can't call a1.WriteData();
because a1
is null. However, we can invoke the action
delegate without a problem, and it will print 4
, because action
holds a reference to the instance with which the method should be called.
When anonymous methods are passed as a delegate in an instance context, the delegate will still hold a reference to the containing class, even though it's not obvious:
public class Container {
private List<int> data = new List<int>() {1,2,3,4,5};
public void PrintItems() {
//There is an implicit reference to an instance of Container here
data.ForEach(s => Console.WriteLine(s));
}
}
In this specific case, it is reasonable to assume that .ForEach
is not storing the delegate internally, which would mean that the instance of Container
and all its data is still being retained. But there is no guarentee of that; the method receiving the delegate might hold onto the delegate and the instance indefinitely.
Static methods, on the other hand, have no instance to reference. The following will not have an implicit reference to the instance of Container
:
public class Container {
private List<int> data = new List<int>() {1,2,3,4,5};
public void PrintItems() {
//Since Console.WriteLine is a static method, there is no implicit reference
data.ForEach(Console.WriteLine);
}
}