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 для обох, у явній реалізації є набагато більше викликів (і створюється створений метод):
.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'
в той час як неявна реалізація більш коротка:
.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);
}
}