14
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?

1
  • In my experience whenever the second version seemed preferable it usually was because of poor naming of the method in question. Commented Jan 6, 2017 at 13:22

4 Answers 4

23

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
3
  • Note that this is the release build of the code from a quick scratch program, so there may be room for further optimization. But this is the default output from Visual Studio.
    – Agent_9191
    Commented Dec 22, 2011 at 14:28
  • 2
    +1 That's because the lambda syntax is actually wrapping the raw method call in an anonymous function <i>for no reason</i>. This is completely pointless, hence you should use the raw method group as the Func<> parameter when it's available.
    – Ed James
    Commented Dec 22, 2011 at 14:39
  • Wow, you get the green tick, for the research!
    – Benjol
    Commented Dec 23, 2011 at 7:01
2

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.

3
  • I'd prefer the labmda syntax for consistency with the cases where it is required. Commented Dec 22, 2011 at 12:25
  • 4
    I'm not in a C# person, but in the languages I've used with lambdas (JavaScript, Scheme and Haskell) people would probably give you the opposite advice. I think that just shows how good style is language-dependent. Commented Dec 22, 2011 at 12:43
  • in what way does it tell you the type? certainly you can be explicit about the type of a lambdas parameter but its far from common to do that, and isnt done in this situation
    – jk.
    Commented Nov 14, 2016 at 14:23
1

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.

1

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);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.