Difference between lambda expression and method group
Asked Answered
A

3

20

What's the difference between

Class1.Method1<Guid, BECustomer>("cId", Facade.Customers.GetSingle);

and

Class1.Method1<Guid, BECustomer>("cId", x => Facade.Customers.GetSingle(x));

?

Resharper suggests to use the first expression.

Accelerometer answered 12/7, 2011 at 10:10 Comment(1)
In C# there's no semantic difference. In Java, a similar type of thing results in different bindings of 'this'! I get bit by this all of the time...Enow
B
25

There is no difference in regards to the result. However, the second one creates an additional redirection: The code will first call your anonymous method the takes one parameter named x and that in turn calls Facade.Customers.GetSingle with that parameter. This redirection has no benefit at all, that's why ReSharper tells you to use the first alternative.

Bicolor answered 12/7, 2011 at 10:11 Comment(8)
That's correct, I think this is just syntactic sugar for lambdas with only one parameter.Madelina
@Vladislav: What do you mean? The first version is not syntactic sugar for the second one. The first one passes the "function pointer" of the GetSingle method, whereas the second one passes the "function pointer" of the anonymous method.Bicolor
It's not restricted to single-parameter lambdas - method groups will work for any matching delegate signature.Variometer
There's a difference with respect to closure. Suppose Facade.Customers changes as time goes by. Then the first one (method group from named method) will keep the original Customers, whereas the second one (anonymous function from lambda) will reflect the new Customers (Customers is re-evaluated by the anonymous function). So in the second one, evaluation is "deferred". If this is hard to understand, try out this code: string test = "12345"; Func<string, bool> f = test.Contains; Func<string, bool> g = x => test.Contains(x); test = "changed!"; bool a = f("34"); bool b = g("34");.Evetteevey
@JeppeStigNielsen: Correct. I didn't include it in this answer because Facade.Customers looked pretty static to me. I explained the difference in more detail in this answer. Thanks.Bicolor
For an example where only method group conversion (not lambda) is allowed, here is an example with a ref parameter. Namely the code static void M1(ref string s) { Func<string, bool> f1 = s.Contains; /* OK */ } is fine; however the code static void M2(ref string s) { Func<string, bool> f2 = x => s.Contains(x); /* Compile-time error, cannot close over 'ref' parameter */ } is illegal.Evetteevey
On the other hand, an example where only lambda (not method group) is allowed can be made when the "target" is a nullable type. These have special boxing conversions (as is well known) which leads to funny examples here. For example: int? a = 42; Func<int> f1 = a.GetValueOrDefault;/* interestingly not allowed */ Func<int> f2 = () => a.GetValueOrDefault();/* fine of course */. Even when both are allowed (for methods inherited by Nullable<> from its base classes), they may behave differently when the "target" HasValue is false. Example: int? a = null; Func<string> g = a.ToString;Evetteevey
@JeppeStigNielsen I thought I knew about method group syntax until I surprised myself by finding out the IL generated for them differs from the "hand-rolled" lambda call, and upon searching for why, find this answer and your's and Daniel's comment. TIL... :-)Showcase
V
16

Behind the scenes, the compiler generates a lot more code if you use the lambda expression. With the method group, it just makes a new delegate pointing to that method:

L_0001: ldstr "cId"
L_0006: ldnull 
L_0007: ldftn void Facade/Customers::GetSingle(valuetype [mscorlib]System.Guid)
L_000d: newobj instance void [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid>::.ctor(object, native int)
L_0012: call void Class1::Method1<valuetype [mscorlib]System.Guid, class BECustomer>(string, class [mscorlib]System.Action`1<!!0>)

With the lambda expression, an anonymous method is created on the class (<Test>b__0 on L_0025) and the delegate references that instead:

L_0018: ldstr "cId"
L_001d: ldsfld class [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid> Class1::CS$<>9__CachedAnonymousMethodDelegate1
L_0022: brtrue.s L_0037
L_0024: ldnull 
L_0025: ldftn void Class1::<Test>b__0(valuetype [mscorlib]System.Guid)
L_002b: newobj instance void [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid>::.ctor(object, native int)
L_0030: stsfld class [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid> Class1::CS$<>9__CachedAnonymousMethodDelegate1
L_0035: br.s L_0037
L_0037: ldsfld class [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid> Class1::CS$<>9__CachedAnonymousMethodDelegate1
L_003c: call void Class1::Method1<valuetype [mscorlib]System.Guid, class BECustomer>(string, class [mscorlib]System.Action`1<!!0>)
Variometer answered 12/7, 2011 at 10:24 Comment(4)
so is there any sort of actual performance difference between the lambda and the method group? I can see 5 more lines of IL, but what kind of real world difference are we talking? a couple ticks on really large data sets?Frore
The worst case would be calling your Method1 for each item in a large set - a new instance of the anonymous class would be created on each iteration. That said, it's unlikely using a lambda instead of method group will measurably impact your app. Only measuring can tell you for sure though.Variometer
The lambda expression generates more code but according to this post, the lambda expression is actually a little faster than the method group and uses less memory because "the Method Group version allocates a new object every single time it is run whereas the lambda version uses an instance (or static, as necessary) field to cache the delegate."Chesterfield
As of C#11, using method group will create a new instance of delegate object and reuse it, so there's no extra unnecessary allocation anymoreJos
B
1

Where your Method1<Guid, BECustomer> accepts a Func<Guid, BECustomer> argument, Func<Guid, BECustomer> is synonymous with:

public delegate BECustomer Func(Guid arg);

In fact, all a Func is is a generic delegate:

public delegate TResult Func<T, TResult>(T arg);

The compiler can analyse your code and determine that your Func<Guid, BECustomer> is compatible with the method group for Facade.Customers.GetSingle because the method signature matches the delegate signature.

This is syntactic sugar and is another example of the compiler doing the grunt work for you.

Bitterweed answered 12/7, 2011 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.