开发者

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

开发者 https://www.devze.com 2023-01-18 15:39 出处:网络
When dealing with something like a List<string> you can write the followi开发者_运维知识库ng:

When dealing with something like a List<string> you can write the followi开发者_运维知识库ng:

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?


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.


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.


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


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.


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.


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.


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

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号