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


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.