Are there any benefits to using a C# method group if available?
Asked Answered
B

7

24

When dealing with something like a List<string> you can write the following:

list.ForEach(x => Console.WriteLine(x));

or you can use a method group to do the same operation:

list.ForEach(Console.WriteLine);

I prefer the second line of code because it looks cleaner to me, but are there any benefits to this?

Bellona answered 1/10, 2010 at 18:20 Comment(2)
Well, ReSharper recommends the second version. So it should be the right one...Hyder
"Someone smarter says thats right" isn't really an explanation of WHY. It may be right. It may be the best answer. But that doesn't answer the question of "why".Hornbill
D
29

Well, let's take a look and see what happens.

static void MethodGroup()
{
    new List<string>().ForEach(Console.WriteLine);
}

static void LambdaExpression()
{
    new List<string>().ForEach(x => Console.WriteLine(x));
}

This gets compiled into the following IL.

.method private hidebysig static void MethodGroup() cil managed
{
    .maxstack 8
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0005: ldnull 
    L_0006: ldftn void [mscorlib]System.Console::WriteLine(string)
    L_000c: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
    L_0011: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>)
    L_0016: ret 
}

.method private hidebysig static void LambdaExpression() cil managed
{
    .maxstack 8
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0005: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
    L_000a: brtrue.s L_001d
    L_000c: ldnull 
    L_000d: ldftn void Sandbox.Program::<LambdaExpression>b__0(string)
    L_0013: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
    L_0018: stsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
    L_001d: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
    L_0022: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>)
    L_0027: ret 
}

Notice how the method group approach creates an Action<T> delegate for one time use and the lambda expression approach creates a hidden anonymous delegate field and does an inline initialization of it if necessary. Notice brtrue instruction at IL_000a.

Dissimulate answered 1/10, 2010 at 18:56 Comment(3)
how can I view the compiled IL ?Kisner
@M.H.: Use ILDASM or Reflector.Dissimulate
@M.H you can use LINQPad to view the compiled ILHypersonic
A
13

There is an extra level of indirection when using the lambda expression. With a non-closure expression like that, you'll simply have an extra method call in-between, as mentioned by others.

There are a few interesting differences though. In the second case, a new delegate instance is being created on each call. For the former, the delegate is created once and cached as a hidden field, so if you're calling a lot you'll save on allocations.

Additionally, if you introduce a local variable into the lambda expression, it becomes a closure and instead of just a local method being generated, a new class will be created to hold this information, meaning an extra allocation there.

Ashling answered 1/10, 2010 at 18:28 Comment(0)
M
9

As others have noted, there is an extra unnecessary layer of indirection induced by the lambda. However, there are subtle language differences as well. For example, in C# 3 generic type inference works differently on M(F) than on M(x=>F(x)) when attempting to perform return type inference.

For details see:

https://learn.microsoft.com/en-us/archive/blogs/ericlippert/c-3-0-return-type-inference-does-not-work-on-method-groups

and the follow-up:

https://learn.microsoft.com/en-us/archive/blogs/ericlippert/method-type-inference-changes-part-zero

Megan answered 1/10, 2010 at 23:37 Comment(0)
D
7

I believe that there is a benefit. In first case you are creating anonymous method which calls Console.Writeline(string) function while in the other case you are just passing the reference to existing function.

Dryad answered 1/10, 2010 at 18:24 Comment(1)
Yup, that was my feeling, too. I imagine it's possible the optimizer may recognize this and strip out the extra, unneeded call, but when it's actually easier to write it the 'better' way, it makes sense to do so, IMO. I wrote a blog post about this topic (unnecessarily using Lambda Expressions like this: andrewbarber.com/post/… )Chair
C
3

Yes; the first actually can cause an unnecessary extra, interim call to happen; passing x in to a method that simply calls Console.WriteLine(x); You don't need to do the first one because Console.WriteLine already is a method which matches the signature that ForEach is looking for.

Chair answered 1/10, 2010 at 18:23 Comment(0)
P
0

Personally I also prefer the second because it's less confusing to debug, but in this case I think it's just a matter of style since they both end up getting the same thing done.

Priestridden answered 1/10, 2010 at 18:24 Comment(0)
M
0

No tangible benefits other than making it more pleasant to people who like method groups, and annoy people who dislike them [should that please you.] Also, it makes your code incompatible with earlier compilers.

-Oisin

Mopey answered 1/10, 2010 at 18:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.