Чи є причина віддати перевагу лямбда-синтаксису, навіть якщо є лише один параметр?


14
List.ForEach(Console.WriteLine);

List.ForEach(s => Console.WriteLine(s));

Для мене різниця суто косметична, але чи є якісь тонкі причини, чому можна віддати перевагу іншому?


На мій досвід, коли друга версія здавалася кращою, зазвичай це було через погану назву методу, про який йде мова.
Роман Райнер

Відповіді:


23

Дивлячись на складений код через 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

Зауважте, що це збірка випуску коду з програми швидкої подряпини, тому може бути місце для подальшої оптимізації. Але це вихід за замовчуванням від Visual Studio.
Agent_9191

2
+1 Це тому, що синтаксис лямбда фактично перетворює виклик необробленого методу в анонімну функцію <i> без жодної причини </i>. Це абсолютно безглуздо, отже, ви повинні використовувати грубу групу методу як параметр Func <>, коли вона доступна.
Ед Джеймс

Ого, ви отримаєте зелений кліщ для дослідження!
Бенжол

2

Я б вважав за краще синтаксис лямбда взагалі . Коли ви це бачите, то він говорить вам, що це за тип. Побачивши Console.WriteLine, вам доведеться запитати IDE, що це за тип. Звичайно, у цьому тривіальному прикладі це очевидно, але в загальному випадку це може бути не так багато.


Я вважаю за краще синтаксис labmda для узгодження з випадками, коли це потрібно.
bunglestink

4
Я не в особі C #, але мовами, якими я користувався з лямбдами (JavaScript, схема та Haskell), люди, ймовірно, дадуть вам протилежну пораду. Я думаю, що це просто показує, наскільки хороший стиль залежить від мови.
Тихон Єлвіс

яким способом він розповідає вам тип? звичайно, ви можете бути чіткими щодо типу параметра лямбдаса, але це далеко не звичайне для цього, і не робиться в цій ситуації
jk.

1

з двох прикладів, які ви навели, вони відрізняються тим, що ви говорите

List.ForEach(Console.WriteLine) 

ви насправді говорите циклу ForEach про використання методу WriteLine

List.ForEach(s => Console.WriteLine(s));

насправді визначає метод, за допомогою якого викличе foreach, і тоді ви говорите йому, з чим тут працювати.

тому для простих вкладишів, якщо ваш метод, який ви збираєтесь викликати, має такий же підпис, що і метод, який викликається, я вважаю за краще не визначати лямбда, я вважаю, що це трохи читабельніше.

для методів з несумісними лямбдахами, безумовно, хороший шлях, якщо припустити, що вони не надто складні.


1

Є дуже сильна причина віддати перевагу першому рядку.

Кожен делегат має 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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.