क्या केवल एक पैरामीटर होने पर भी लैम्बडा सिंटैक्स पसंद करने का कोई कारण है?


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);
            }
            );
        }
    }
}

यदि आप दोनों के लिए आईएल कॉल स्टैक को देखते हैं, तो स्पष्ट कार्यान्वयन में बहुत अधिक कॉल हैं (और एक उत्पन्न विधि बनाता है):

.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 से डिफ़ॉल्ट आउटपुट है।
एजेंट_9191

2
+1 ऐसा इसलिए है क्योंकि लैम्बडा सिंटैक्स वास्तव में एक अनाम फ़ंक्शन में कच्चे विधि कॉल को <i> बिना किसी कारण के </ i> लपेट रहा है। यह पूरी तरह से व्यर्थ है, इसलिए आपको उपलब्ध होने पर कच्चे विधि समूह को फंक <> पैरामीटर के रूप में उपयोग करना चाहिए।
एड जेम्स

वाह, आपको हरी टिक मिलती है, शोध के लिए!
बेंजोल

2

मैं सामान्य रूप से लंबोदर सिंटैक्स पसंद करूंगा । जब आप उसे देखते हैं, तो यह बताता है कि प्रकार क्या है। जब आप देखते हैं Console.WriteLine, तो आपको आईडीई से पूछना होगा कि यह किस प्रकार का है। बेशक, इस तुच्छ उदाहरण में, यह स्पष्ट है, लेकिन सामान्य मामले में, यह इतना नहीं हो सकता है।


मैं उन मामलों के साथ सामंजस्य के लिए लैम्ब्डा सिंटैक्स पसंद करूँगा जहाँ इसकी आवश्यकता है।
बंगलेस्टिंक

4
मैं एक C # व्यक्ति में नहीं हूं, लेकिन भाषाओं में मैंने लैम्बदास (जावास्क्रिप्ट, स्कीम और हास्केल) के साथ उपयोग किया है, लोग शायद आपको विपरीत सलाह देंगे। मुझे लगता है कि बस पता चलता है कि भाषा की निर्भरता कितनी अच्छी है।
तिखन जेल्विस

किस तरह से यह आपको प्रकार बताता है? निश्चित रूप से आप लैम्ब्डा पैरामीटर के प्रकार के बारे में स्पष्ट हो सकते हैं लेकिन ऐसा करने के लिए सामान्य से बहुत दूर है, और इस स्थिति में नहीं किया जाता है
जेके।

1

आपके द्वारा दिए गए दो उदाहरणों के साथ जब आप कहते हैं कि वे इसमें भिन्न हैं

List.ForEach(Console.WriteLine) 

आप वास्तव में ForEach लूप को विधि लाइलाइन का उपयोग करने के लिए कह रहे हैं

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

वास्तव में एक विधि को परिभाषित कर रहा है जिसे फ़ॉरच कॉल करेगा और फिर आप इसे बता रहे हैं कि वहां क्या संभालना है।

इसलिए साधारण एक लाइनर के लिए यदि आपकी विधि जिसे आप कॉल करने जा रहे हैं, उसी हस्ताक्षर को वहन करने की विधि है जिसे पहले से ही कहा जाता है मैं लैंबडा को परिभाषित नहीं करना पसंद करूंगा, मुझे लगता है कि यह थोड़ा अधिक पठनीय है।

असंगत लैम्ब्डा वाले तरीकों के लिए निश्चित रूप से जाने का एक अच्छा तरीका है, यह मानते हुए कि वे अत्यधिक जटिल नहीं हैं।


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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.