2013-07-17 14 views
10

क्या कोई अज्ञात विधि और लैम्ब्डा अभिव्यक्तियों के बीच संक्षिप्त अंतर प्रदान कर सकता है?
बेनामी विधियों बनाम लैम्ब्डा अभिव्यक्ति

गुमनाम विधि का उपयोग:

private void DoSomeWork() 
{ 
    if (textBox1.InvokeRequired) 
    { 
     //textBox1.Invoke((Action)(() => textBox1.Text = "test")); 
     textBox1.Invoke((Action)delegate { textBox1.Text = "test"; }); 
    } 
} 

यह केवल एक सामान्य लैम्ब्डा अभिव्यक्ति है एक जोरदार टाइप किया प्रतिनिधि के लिए डाली जा रहा है या इसका अधिक मुखौटे है।

मुझे अच्छी तरह पता है कि जैसे एक जोरदार टाइप किया प्रतिनिधि का पालन प्रकार System.Delegate की एक पैरामीटर के रूप

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName) 

पर्याप्त हूँ, लेकिन गुमनाम विधि के विचार मेरे लिए नहीं बल्कि नया है।

+1

क्या आपने इसे देखा है? और संभवतः अन्य प्रश्न? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –

+0

मैं पूरी तरह से समझता हूं कि एक अनाम विधि/लैम्ब्डा अभिव्यक्ति क्या है और वहां उपयोग है, मेरा सवाल यह है कि क्या वे अज्ञात से किसी भी माध्यम से भिन्न होते हैं प्रतिनिधि या वे वही हैं – sarepta

+2

ठीक है, मैंने अभी एक **** लोड टेक्स्ट लिखा है, इसलिए उम्मीद है कि आपको अपना जवाब मिल जाएगा, अगर मुझे नहीं पता :) –

उत्तर

24

एक अज्ञात विधि है? क्या यह वास्तव में अनाम है? इसका कोई नाम है? सभी अच्छे प्रश्न, तो चलिए उनके साथ शुरू करते हैं और साथ ही साथ लैम्ब्डा अभिव्यक्तियों तक पहुंचते हैं।

public void TestSomething() 
{ 
    Test(delegate { Debug.WriteLine("Test"); }); 
} 

क्या वास्तव में होता है:

आप इस कब मिलेंगे?

Debug.WriteLine("Test"); 

और कहा कि अलग एक विधि में:

संकलक पहली विधि है, जो यह है की "शरीर" लेने का फैसला।

  1. कहाँ मैं विधि रखना चाहिए:

    दो सवाल संकलक अब जवाब देने के लिए है?

  2. विधि का हस्ताक्षर कैसा दिखना चाहिए?

दूसरा प्रश्न आसानी से उत्तर दिया गया है। delegate { भाग इसका उत्तर देता है। विधि कोई पैरामीटर (delegate और { के बीच कुछ भी नहीं है) लेता है, और के बाद से हम इसके नाम (इसलिए "गुमनाम" भाग) के बारे में परवाह नहीं है, हम इस तरह के रूप में प्रणाली की घोषणा कर सकते हैं:

public void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

लेकिन क्यों किया यह सब कुछ करो?

चलिए देखते हैं कि Action वास्तव में क्या प्रतिनिधि है।

एक प्रतिनिधि यह है कि, यदि हम एक पल के लिए इस तथ्य की उपेक्षा करते हैं कि इसमें प्रतिनिधि हैं।नेट वास्तव में दो बातें करने के लिए कई की सूची से जुड़े हुए हैं एकल "प्रतिनिधियों", एक संदर्भ (सूचक):

  1. एक वस्तु उदाहरण
  2. कि वस्तु उदाहरण पर एक विधि

तो, के साथ कि ज्ञान, कोड की पहली टुकड़ा वास्तव में इस के रूप में लिखा जा सकता है:

public void TestSomething() 
{ 
    Test(new Action(this.SomeOddMethod)); 
} 

private void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

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

उदाहरण के लिए, मान लें कि उपरोक्त कोड वास्तव में एक विशाल वस्तु का हिस्सा था, लेकिन एक वस्तु जो केवल अस्थायी रूप से रहती है। यह भी मान लें कि Test उस प्रतिनिधि को कहीं भी स्टोर करेगा, यह लंबे समय तक जी रहेगा। वह "लंबा समय" उस विशाल वस्तु के जीवन को अपने आप में बांध देगा, इसके लिए एक लंबे समय तक संदर्भ भी रखेगा, शायद अच्छा नहीं।

तो संकलक सिर्फ एक विधि बनाने से अधिक करता है, यह इसे पकड़ने के लिए एक वर्ग भी बनाता है। यह पहला सवाल है, मुझे इसे कहां रखना चाहिए?

कोड ऊपर इस प्रकार इस प्रकार के रूप में लिखा जा सकता है:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    private void SomeOddMethod() 
    { 
     Debug.WriteLine("Test"); 
    } 
} 

यही है, इस उदाहरण के लिए, क्या एक गुमनाम विधि वास्तव में सभी के बारे में है।

हालात में थोड़ा और अधिक बालों मिल अगर आप स्थानीय चर का उपयोग शुरू, इस उदाहरण पर विचार करें:

public void Test() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

यह वही है हुड के नीचे होता है, या कम से कम इसके बहुत करीब कुछ:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    temp.x = 10; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    public int x; 

    private void SomeOddMethod() 
    { 
     Debug.WriteLine("x=" + x); 
    } 
} 

कंपाइलर एक वर्ग बनाता है, उस वर्ग में विधि के सभी चर को लिफ्ट करता है, और अज्ञात प्रकार के फ़ील्ड तक पहुंच के लिए स्थानीय चरों तक पहुंच को फिर से लिखता है।

क्लास का नाम है, और विधि, थोड़ा अजीब है, के LINQPad पूछना यह क्या होगा करते हैं:

void Main() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

public void Test(Action action) 
{ 
    action(); 
} 

अगर मैं उत्पादन के लिए LINQPad पूछना इस कार्यक्रम का आईएल (मध्यवर्ती भाषा),

// var temp = new UserQuery+<>c__DisplayClass1(); 
IL_0000: newobj  UserQuery+<>c__DisplayClass1..ctor 
IL_0005: stloc.0  // CS$<>8__locals2 
IL_0006: ldloc.0  // CS$<>8__locals2 

// temp.x = 10; 
IL_0007: ldc.i4.s 0A 
IL_0009: stfld  UserQuery+<>c__DisplayClass1.x 

// var action = new Action(temp.<Main>b__0); 
IL_000E: ldarg.0  
IL_000F: ldloc.0  // CS$<>8__locals2 
IL_0010: ldftn  UserQuery+<>c__DisplayClass1.<Main>b__0 
IL_0016: newobj  System.Action..ctor 

// Test(action); 
IL_001B: call  UserQuery.Test 

Test: 
IL_0000: ldarg.1  
IL_0001: callvirt System.Action.Invoke 
IL_0006: ret   

<>c__DisplayClass1.<Main>b__0: 
IL_0000: ldstr  "x=" 
IL_0005: ldarg.0  
IL_0006: ldfld  UserQuery+<>c__DisplayClass1.x 
IL_000B: box   System.Int32 
IL_0010: call  System.String.Concat 
IL_0015: call  System.Diagnostics.Debug.WriteLine 
IL_001A: ret   

<>c__DisplayClass1..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: ret   

यहाँ आप देख सकते हैं कि क्लास का नाम UserQuery+<>c__DisplayClass1 है, और विधि का नाम <Main>b__0 है: मैं इस मिलता है। मैंने सी # कोड में संपादित किया जिसने इस कोड का निर्माण किया, LINQPad उपरोक्त उदाहरण में आईएल के अलावा कुछ भी नहीं उत्पन्न करता है।

यह सुनिश्चित करने के लिए संकेतों से कम और अधिक से अधिक संकेत हैं कि आप दुर्घटना से नहीं कर सकते हैं एक प्रकार और/या विधि जो आपके लिए उत्पादित कंपाइलर से मेल खाता है।

तो मूल रूप से यह एक अज्ञात विधि क्या है।

तो यह क्या है?

Test(() => Debug.WriteLine("Test")); 

वैसे, इस मामले में यह एक ही है, यह एक गुमनाम विधि के उत्पादन के लिए एक शॉर्टकट है।

आप दो तरह से यह लिख सकते हैं:

() => { ... code here ... } 
() => ... single expression here ... 

अपनी पहली रूप में आप सभी कोड आप एक सामान्य विधि शरीर में करना होगा लिख ​​सकते हैं। अपने दूसरे रूप में आपको एक अभिव्यक्ति या कथन लिखने की अनुमति है। इस रूप में

() => ... 

उसी तरह:

हालांकि, इस मामले में संकलक इस व्यवहार करेगा

delegate { ... } 

वे अभी भी गुमनाम तरीकों रहे हैं, यह सिर्फ इतना है कि () => वाक्य रचना इसे पाने के लिए एक शॉर्टकट है।

तो यदि यह प्राप्त करने का शॉर्टकट है, तो हमारे पास यह क्यों है?

ठीक है, यह उस उद्देश्य के लिए जीवन को थोड़ा आसान बनाता है, जिसे LINQ है।

var customers = from customer in db.Customers 
       where customer.Name == "ACME" 
       select customer.Address; 

इस कोड के रूप में लिखा है इस प्रकार है::

var customers = 
    db.Customers 
     .Where(customer => customer.Name == "ACME") 
     .Select(customer => customer.Address"); 

आप delegate { ... } सिंटैक्स का उपयोग करना हो तो आप return ... और इतने के साथ भाव पुनर्लेखन करना होगा

इस LINQ बयान पर विचार करें पर, और वे अधिक मज़ाकिया लगेंगे। इस प्रकार लैम्ब्डा सिंटैक्स को उपरोक्त जैसे कोड लिखते समय प्रोग्रामर के लिए जीवन आसान बनाने के लिए जोड़ा गया था।

तो अभिव्यक्ति क्या हैं?

अब तक मैं नहीं दिखाया Test कैसे परिभाषित किया गया है, लेकिन इसके बाद के संस्करण कोड के लिए Test को परिभाषित करते हैं:

public void Test(Action action) 

यह पर्याप्त होना चाहिए। यह कहता है कि "मुझे एक प्रतिनिधि की आवश्यकता है, यह प्रकार की कार्रवाई है (कोई पैरामीटर नहीं ले रहा है, कोई मूल्य नहीं लौटा रहा है)"।

हालांकि, माइक्रोसॉफ्ट भी इस विधि को परिभाषित करने के लिए एक अलग तरीके कहा:

public void Test(Expression<Func<....>> expr) 

ध्यान दें कि मैं एक हिस्सा वहाँ गिरा दिया, .... हिस्सा है, की पीठ कि को मिलता है।

इस कोड, इस कॉल के साथ रखा:

Test(() => x + 10); 

वास्तव में एक प्रतिनिधि में पारित नहीं होगा, न ही कुछ भी है कि (तुरंत) कहा जा सकता है।इसके बजाय, संकलक कुछ के लिए इस कोड को फिर से लिखने होगा समान (लेकिन नहीं सब पर) की तरह नीचे कोड:

var operand1 = new VariableReferenceOperand("x"); 
var operand2 = new ConstantOperand(10); 
var expression = new AdditionOperator(operand1, operand2); 
Test(expression); 

मूल रूप से संकलक एक Expression<Func<...>> वस्तु का निर्माण होगा, चर के संदर्भों वाले, शाब्दिक मान , ऑपरेटरों का इस्तेमाल इत्यादि। और उस ऑब्जेक्ट पेड़ को विधि में पास करें।

क्यों?

ठीक है, ऊपर db.Customers.Where(...) भाग पर विचार करें।

क्या यह अच्छा नहीं होगा कि, सभी ग्राहकों (और उनके सभी डेटा) को डेटाबेस से क्लाइंट में डाउनलोड करने के बजाय, उन सभी के माध्यम से लूपिंग करना, यह पता लगाना कि किस ग्राहक का सही नाम है, आदि कोड वास्तव में होगा डेटाबेस को एक ही, सही, ग्राहक को एक बार खोजने के लिए कहें?

अभिव्यक्ति के पीछे यह उद्देश्य है। एंटीटी फ्रेमवर्क, लिंक 2 एसक्यूएल, या किसी अन्य ऐसे LINQ- सहायक डेटाबेस परत, उस अभिव्यक्ति को ले लेंगे, इसका विश्लेषण करें, इसे अलग करें, और डेटाबेस के विरुद्ध निष्पादित करने के लिए एक उचित प्रारूपित एसक्यूएल लिखें।

यह कभी कर सकता है अगर हम अभी भी आईएल युक्त विधियों को प्रतिनिधि दे रहे हैं। यह केवल बातें की एक जोड़ी की वजह से ऐसा कर सकते हैं: (आदि कोई बयान,)

  1. वाक्य रचना एक Expression<Func<...>> के लिए उपयुक्त एक लैम्ब्डा अभिव्यक्ति में अनुमति दी सीमित है
  2. लैम्ब्डा वाक्यविन्यास कर्ली कोष्ठक के बिना, जो संकलक इस कोड का एक सरल रूप है कि बताता है

तो चलिए को संक्षेप में दोहराएँ:

  1. बेनामी तरीकों वास्तव में नहीं कि सभी अनाम, वे एक नामित प्रकार के रूप में, अंत कर रहे हैं एक नामित विधि के साथ, केवल आप उन चीजों को नाम देने की जरूरत नहीं है अपने आप को
  2. यह हुड कि चीजें ले जाता है के तहत संकलक जादू का एक बहुत है चारों ओर आप के लिए
  3. भाव और प्रतिनिधि एक ही बातें
  4. भाव चौखटे पता करने के लिए क्या कोड करता है और कैसे चाहता है के लिए हैं में से कुछ को देखने के लिए दो तरीके हैं की जरूरत नहीं है, इसलिए है कि, ताकि वे प्रक्रिया को अनुकूलित करने के लिए उस ज्ञान का उपयोग कर सकते हैं (जैसे SQL कथन लिखना)
  5. प्रतिनिधियों के लिए हैं चौखटे हैं कि केवल विधि

फुटनोट कॉल करने के लिए सक्षम किया जा रहा बारे में चिंतित:

  1. इस तरह के एक सरल अभिव्यक्ति के लिए .... हिस्सा वापसी मान के प्रकार आप प्राप्त करने के लिए है अभिव्यक्ति से। () => ... simple expression ... केवल अभिव्यक्ति देता है, जो कि कुछ मान देता है, और यह कई कथन नहीं हो सकता है।इस प्रकार, एक वैध अभिव्यक्ति प्रकार यह है: Expression<Func<int>>, मूल रूप से, अभिव्यक्ति एक फ़ंक्शन (विधि) एक पूर्णांक मान लौटा रही है।

    ध्यान दें कि "अभिव्यक्ति जो मूल्य लौटाती है" Expression<...> पैरामीटर या प्रकारों की सीमा है, लेकिन प्रतिनिधि नहीं। यह पूरी तरह से कानूनी कोड है अगर Test के पैरामीटर प्रकार एक Action है:

    Test(() => Debug.WriteLine("Test")); 
    

    जाहिर है, Debug.WriteLine("Test") कुछ भी वापस नहीं करता है, लेकिन यह कानूनी है। यदि विधि Test को अभिव्यक्ति की आवश्यकता है, तो ऐसा नहीं होगा, क्योंकि अभिव्यक्ति को एक मान वापस करना होगा।

+2

शब्द "अज्ञात" वास्तव में बहुत अच्छा है इसके लिए शब्द विचार करें कि कोई व्यक्ति किसी अच्छे कारण के लिए धन का एक टन दान करता है। जाहिर है कि उस व्यक्ति का नाम है, यह सिर्फ लोगों को पैसे नहीं मिल रहा है। इस मामले में, विधि का नाम आपको ज्ञात नहीं है, लेकिन वास्तव में इसका नाम है। –

+0

उत्कृष्ट प्रवचन, लेकिन अभिव्यक्तियों के रूप में इनसे निपटने का प्रयास करते समय सिंटैक्टिक चीनी से परे एक सूक्ष्म अंतर होता है। क्या होता है यदि आप किसी अज्ञात प्रतिनिधि को किसी विधि में पास करने का प्रयास करते हैं जो केवल 'अभिव्यक्ति >' लेता है? –

+0

आप ऐसा नहीं कर सकते। आपको एक कंपाइलर त्रुटि मिलती है, "एक अज्ञात विधि अभिव्यक्ति को अभिव्यक्ति वृक्ष में परिवर्तित नहीं किया जा सकता है"। यह 'टेस्ट (प्रतिनिधि {वापसी 10;}) से था;' जब विधि को 'सार्वजनिक शून्य परीक्षा' (अभिव्यक्ति > expr) के रूप में घोषित किया गया था। ध्यान दें, मैं यह नहीं कह रहा हूं कि ऐसी चीजें नहीं हैं जिन पर मैंने स्पर्श नहीं किया है, इसलिए यदि आप कुछ पा सकते हैं तो मुझे उन पर विस्तार करने में खुशी होगी, लेकिन एक अभिव्यक्ति की आवश्यकता वाले एक प्रतिनिधि को पास करना एक नो- चले जाओ। –

1

सटीक होने के लिए, आप 'अज्ञात प्रतिनिधि' नाम का क्या नाम वास्तव में एक अज्ञात विधि है।

ठीक है, दोनों लैम्बडा और अज्ञात विधियां सिंटैक्स चीनी हैं। कंपाइलर आपके लिए कम से कम 'सामान्य' विधि उत्पन्न करेगा, हालांकि कभी-कभी (बंद होने के मामले में) यह एक नेस्टेड क्लास उत्पन्न करेगा जिसमें अब तक अज्ञात विधि नहीं होगी।

5

एक सूक्ष्म अंतर है जिसके बारे में आपको अवगत होना चाहिए। निम्नलिखित प्रश्नों पर विचार करें (प्रोवर्बियल नॉर्थविंड का उपयोग करके)।

Customers.Where(delegate(Customers c) { return c.City == "London";}); 
Customers.Where(c => c.City == "London"); 

पहला व्यक्ति अज्ञात प्रतिनिधि का उपयोग करता है और दूसरा लैम्ब्डा अभिव्यक्ति का उपयोग करता है। यदि आप दोनों के परिणामों का मूल्यांकन करते हैं तो आप एक ही चीज़ देखेंगे। हालांकि, जेनरेट किए गए एसक्यूएल को देखते हुए, हम एक अलग कहानी देखेंगे। पहले

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 

उत्पन्न करता है जबकि दूसरा पहले मामले में उत्पन्न

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 
WHERE [t0].[City] = @p0 

सूचना है, जहां खंड डेटाबेस तक नहीं पहुंचेगी। ऐसा क्यों है? कंपाइलर यह निर्धारित करने में सक्षम है कि लैम्ब्डा अभिव्यक्ति एक साधारण सिंगल-लाइन अभिव्यक्ति है जिसे अभिव्यक्ति वृक्ष के रूप में रखा जा सकता है, जबकि अनाम प्रतिनिधि एक लैम्ब्डा अभिव्यक्ति नहीं है और इस प्रकार Expression<Func<T>> के रूप में लपेटा नहीं जा सकता है। नतीजतन, पहले मामले में, जहां एक्सटेंशन विधि के लिए सबसे अच्छा मिलान वह है जो IQueryable संस्करण के बजाय IENumerable को बढ़ाता है जिसके लिए Expression<Func<T, bool>> की आवश्यकता होती है।

इस बिंदु पर, अज्ञात प्रतिनिधि के लिए बहुत कम उपयोग है। यह अधिक verbose और कम लचीला है। आम तौर पर, मैं हमेशा अज्ञात प्रतिनिधि वाक्यविन्यास पर लैम्ब्डा सिंटैक्स का उपयोग करने की सिफारिश करता हूं और पार्सबिलिटी और सिंटैक्टिक टेर्सनेस भी उठाता हूं।

संबंधित मुद्दे