List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Для меня разница чисто косметическая, но есть ли какие-то тонкие причины, по которым одно может быть предпочтительнее другого?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Для меня разница чисто косметическая, но есть ли какие-то тонкие причины, по которым одно может быть предпочтительнее другого?
Ответы:
Глядя на скомпилированный код через ILSpy, на самом деле есть разница в двух ссылках. Для упрощенной программы, подобной этой:
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 декомпилирует это как:
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);
}
);
}
}
}
Если вы посмотрите на стек вызовов IL для обоих, реализация Explicit имеет намного больше вызовов (и создает сгенерированный метод):
.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'
в то время как реализация Implicit более краткая:
.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
Я бы предпочел лямбда-синтаксис в целом . Когда вы видите это, тогда он говорит вам, что это за тип. Когда вы видите Console.WriteLine
, вы должны спросить IDE, какой это тип. Конечно, в этом тривиальном примере это очевидно, но в общем случае это может быть не так уж и много.
с двумя примерами, которые вы дали, они отличаются тем, когда вы говорите
List.ForEach(Console.WriteLine)
вы на самом деле говорите циклу ForEach использовать метод WriteLine
List.ForEach(s => Console.WriteLine(s));
на самом деле определяет метод, который будет вызывать foreach, а затем вы говорите ему, что там делать.
так что для простых однострочников, если ваш метод, который вы собираетесь вызывать, имеет ту же сигнатуру, что и метод, который вызывается уже, я бы предпочел не определять лямбду, я думаю, что это немного более читабельно.
потому что методы с несовместимыми лямбдами - это, безусловно, хороший путь, если они не слишком сложны.
Есть очень веская причина отдать предпочтение первой строчке.
Каждый делегат имеет Target
свойство, которое позволяет делегатам ссылаться на методы экземпляра даже после того, как экземпляр вышел из области видимости.
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;
Мы не можем позвонить, a1.WriteData();
потому что a1
это ноль. Однако мы можем action
без проблем вызвать делегат, и он напечатает 4
, потому что action
содержит ссылку на экземпляр, с которым должен быть вызван метод.
Когда анонимные методы передаются в качестве делегата в контексте экземпляра, делегат все равно будет содержать ссылку на содержащий класс, хотя это не очевидно:
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));
}
}
В этом конкретном случае разумно предположить, что .ForEach
не хранит делегата внутри, что будет означать, что экземпляр Container
и все его данные все еще сохраняются. Но нет никакой гарантии этого; метод, получающий делегат, может удерживать делегат и экземпляр неопределенно долго.
Статические методы, с другой стороны, не имеют экземпляра для ссылки. Следующее не будет иметь неявную ссылку на экземпляр 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);
}
}