По моему опыту, когда вторая версия казалась предпочтительной, обычно это происходило из-за плохого наименования рассматриваемого метода.
Роман Рейнер
Ответы:
23
Глядя на скомпилированный код через ILSpy, на самом деле есть разница в двух ссылках. Для упрощенной программы, подобной этой:
namespace ScratchLambda{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;internalclassProgram{privatestaticvoidMain(string[] args){varlist=Enumerable.Range(1,10).ToList();ExplicitLambda(list);ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(Console.WriteLine);}privatestaticvoidExplicitLambda(List<int>list){list.ForEach(s =>Console.WriteLine(s));}}}
ILSpy декомпилирует это как:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda{internalclassProgram{privatestaticvoidMain(string[] args){List<int>list=Enumerable.Range(1,10).ToList<int>();Program.ExplicitLambda(list);Program.ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(newAction<int>(Console.WriteLine));}privatestaticvoidExplicitLambda(List<int>list){list.ForEach(delegate(int s){Console.WriteLine(s);});}}}
Если вы посмотрите на стек вызовов IL для обоих, реализация Explicit имеет намного больше вызовов (и создает сгенерированный метод):
.method private hidebysig staticvoidExplicitLambda(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 voidScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
IL_000f: newobj instance voidclass[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 voidclass[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 staticvoid'<ExplicitLambda>b__0'(int32 s
) cil managed
{.custom instance void[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()=(01000000)// 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'
Обратите внимание, что это сборка выпуска кода из быстрой программы, поэтому может быть место для дальнейшей оптимизации. Но это вывод по умолчанию из Visual Studio.
Agent_9191
2
+1 Это потому, что лямбда-синтаксис фактически оборачивает необработанный вызов метода в анонимную функцию <i> без причины </ i>. Это совершенно бессмысленно, поэтому вы должны использовать необработанную группу методов в качестве параметра Func <>, когда он доступен.
Эд Джеймс
Ух ты, ты получишь зеленую галочку за исследования!
Бенджол
2
Я бы предпочел лямбда-синтаксис в целом . Когда вы видите это, тогда он говорит вам, что это за тип. Когда вы видите Console.WriteLine, вы должны спросить IDE, какой это тип. Конечно, в этом тривиальном примере это очевидно, но в общем случае это может быть не так уж и много.
Я бы предпочел синтаксис labmda для соответствия со случаями, когда это требуется.
bunglestink
4
Я не специалист по C #, но на языках, которые я использовал с лямбдами (JavaScript, Scheme и Haskell), люди могли бы дать вам противоположный совет. Я думаю, это просто показывает, насколько хороший стиль зависит от языка.
Тихон Джелвис
каким образом он говорит вам тип? конечно, вы можете явно указывать тип параметра лямбда-выражения, но это далеко не принято делать в этой ситуации
jk.
1
с двумя примерами, которые вы дали, они отличаются тем, когда вы говорите
List.ForEach(Console.WriteLine)
вы на самом деле говорите циклу ForEach использовать метод WriteLine
List.ForEach(s =>Console.WriteLine(s));
на самом деле определяет метод, который будет вызывать foreach, а затем вы говорите ему, что там делать.
так что для простых однострочников, если ваш метод, который вы собираетесь вызывать, имеет ту же сигнатуру, что и метод, который вызывается уже, я бы предпочел не определять лямбду, я думаю, что это немного более читабельно.
потому что методы с несовместимыми лямбдами - это, безусловно, хороший путь, если они не слишком сложны.
Есть очень веская причина отдать предпочтение первой строчке.
Каждый делегат имеет Targetсвойство, которое позволяет делегатам ссылаться на методы экземпляра даже после того, как экземпляр вышел из области видимости.
Мы не можем позвонить, a1.WriteData();потому что a1это ноль. Однако мы можем actionбез проблем вызвать делегат, и он напечатает 4, потому что actionсодержит ссылку на экземпляр, с которым должен быть вызван метод.
Когда анонимные методы передаются в качестве делегата в контексте экземпляра, делегат все равно будет содержать ссылку на содержащий класс, хотя это не очевидно:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//There is an implicit reference to an instance of Container here
data.ForEach(s =>Console.WriteLine(s));}}
В этом конкретном случае разумно предположить, что .ForEachне хранит делегата внутри, что будет означать, что экземпляр Containerи все его данные все еще сохраняются. Но нет никакой гарантии этого; метод, получающий делегат, может удерживать делегат и экземпляр неопределенно долго.
Статические методы, с другой стороны, не имеют экземпляра для ссылки. Следующее не будет иметь неявную ссылку на экземпляр Container:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//Since Console.WriteLine is a static method, there is no implicit reference
data.ForEach(Console.WriteLine);}}
Ответы:
Глядя на скомпилированный код через ILSpy, на самом деле есть разница в двух ссылках. Для упрощенной программы, подобной этой:
ILSpy декомпилирует это как:
Если вы посмотрите на стек вызовов IL для обоих, реализация Explicit имеет намного больше вызовов (и создает сгенерированный метод):
в то время как реализация Implicit более краткая:
источник
Я бы предпочел лямбда-синтаксис в целом . Когда вы видите это, тогда он говорит вам, что это за тип. Когда вы видите
Console.WriteLine
, вы должны спросить IDE, какой это тип. Конечно, в этом тривиальном примере это очевидно, но в общем случае это может быть не так уж и много.источник
с двумя примерами, которые вы дали, они отличаются тем, когда вы говорите
вы на самом деле говорите циклу ForEach использовать метод WriteLine
на самом деле определяет метод, который будет вызывать foreach, а затем вы говорите ему, что там делать.
так что для простых однострочников, если ваш метод, который вы собираетесь вызывать, имеет ту же сигнатуру, что и метод, который вызывается уже, я бы предпочел не определять лямбду, я думаю, что это немного более читабельно.
потому что методы с несовместимыми лямбдами - это, безусловно, хороший путь, если они не слишком сложны.
источник
Есть очень веская причина отдать предпочтение первой строчке.
Каждый делегат имеет
Target
свойство, которое позволяет делегатам ссылаться на методы экземпляра даже после того, как экземпляр вышел из области видимости.Мы не можем позвонить,
a1.WriteData();
потому чтоa1
это ноль. Однако мы можемaction
без проблем вызвать делегат, и он напечатает4
, потому чтоaction
содержит ссылку на экземпляр, с которым должен быть вызван метод.Когда анонимные методы передаются в качестве делегата в контексте экземпляра, делегат все равно будет содержать ссылку на содержащий класс, хотя это не очевидно:
В этом конкретном случае разумно предположить, что
.ForEach
не хранит делегата внутри, что будет означать, что экземплярContainer
и все его данные все еще сохраняются. Но нет никакой гарантии этого; метод, получающий делегат, может удерживать делегат и экземпляр неопределенно долго.Статические методы, с другой стороны, не имеют экземпляра для ссылки. Следующее не будет иметь неявную ссылку на экземпляр
Container
:источник