आप Func <T> के बजाय अभिव्यक्ति <Func <T >> का उपयोग क्यों करेंगे?


948

मैं लंबोदर Funcऔर Actionप्रतिनिधियों को समझता हूं । लेकिन भाव मुझे स्टंप करते हैं।

आप किन परिस्थितियों में Expression<Func<T>>एक सादे पुराने की बजाय उपयोग करेंगे Func<T>?


14
फंक <> को सी # कंपाइलर स्तर पर एक विधि में बदल दिया जाएगा, एक्सप्रेशन <फंक <> को सीधे कोड संकलित करने के बाद एमएसआईएल स्तर पर निष्पादित किया जाएगा, यही कारण है कि यह तेज है
वलीड एके

1
उत्तर के अलावा, csharp भाषा विनिर्देश "4.6 अभिव्यक्ति के पेड़ के प्रकार" संदर्भ को पार करने में सहायक है
djeikyb

जवाबों:


1133

जब आप लैम्ब्डा एक्सप्रेशन को एक्सप्रेशन ट्री के रूप में मानना ​​चाहते हैं और उन्हें क्रियान्वित करने के बजाय उनके अंदर देखते हैं। उदाहरण के लिए, LINQ to SQL अभिव्यक्ति प्राप्त करता है और इसे समतुल्य SQL कथन में परिवर्तित करता है और इसे सर्वर (लैम्बडा को निष्पादित करने के बजाय) में जमा करता है।

वैचारिक रूप से, Expression<Func<T>>है पूरी तरह से अलग से Func<T>Func<T>निरूपित करता है delegateजो एक विधि के लिए बहुत ज्यादा सूचक है और एक लंबोदर अभिव्यक्ति के लिए एक पेड़ डेटा संरचनाExpression<Func<T>> को दर्शाता है । इस पेड़ की संरचना का वर्णन है कि एक लंबोदर अभिव्यक्ति वास्तविक चीज़ करने के बजाय क्या करता है। यह मूल रूप से अभिव्यक्ति, चर, विधि कॉल की संरचना के बारे में डेटा रखता है, ... (उदाहरण के लिए यह जानकारी रखता है जैसे कि यह लंबा कुछ स्थिर + कुछ पैरामीटर है)। आप इस विवरण का उपयोग इसे वास्तविक विधि में बदलने के लिए कर सकते हैं (इसके साथ ) या इसके साथ अन्य सामान (जैसे LINQ to SQL उदाहरण) कर सकते हैं। लंबोदर को गुमनाम तरीके और अभिव्यक्ति के पेड़ के रूप में मानने का कार्य विशुद्ध रूप से एक संकलन का समय है।Expression.Compile

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

प्रभावी रूप से एक IL पद्धति का संकलन करेगा जिसमें कुछ भी नहीं मिलता है और 10 रिटर्न करता है।

Expression<Func<int>> myExpression = () => 10;

एक डेटा संरचना में परिवर्तित हो जाएगा, जो एक अभिव्यक्ति का वर्णन करता है जिसे कोई पैरामीटर नहीं मिलता है और 10 मान लौटाता है:

अभिव्यक्ति बनाम फंक बड़ी छवि

जबकि वे दोनों संकलन समय पर समान दिखते हैं, जो संकलक उत्पन्न करता है वह पूरी तरह से अलग है


95
तो, दूसरे शब्दों में, Expressionएक निश्चित प्रतिनिधि के बारे में मेटा-जानकारी शामिल है।
बर्टेल

39
@ रॉबर्टल वास्तव में, नहीं। प्रतिनिधि बिल्कुल भी शामिल नहीं है। एक प्रतिनिधि के साथ किसी भी संबंध में कारण यह है कि आप एक प्रतिनिधि को अभिव्यक्ति संकलित कर सकते हैं - या अधिक सटीक होने के लिए, इसे एक विधि के लिए संकलित करें और प्रतिनिधि को वापसी मूल्य के रूप में उस पद्धति पर लाएं। लेकिन अभिव्यक्ति का पेड़ ही डेटा है। प्रतिनिधि तब मौजूद नहीं होता है जब आप Expression<Func<...>>केवल के बजाय उपयोग करते हैं Func<...>
लुआं

5
@ काइल डेलाने (isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }ऐसी अभिव्यक्ति एक अभिव्यक्ति है, अगर-बयान के लिए शाखाएं बनाई जाती हैं।
मट्टियो मार्सियानो - MSCP

3
@bertl डेलिगेट वह है जो सीपीयू देखता है (एक वास्तुकला का निष्पादन योग्य कोड), अभिव्यक्ति वह है जो संकलक देखता है (स्रोत कोड का एक और प्रारूप, लेकिन अभी भी स्रोत कोड)।
कोडवर्ड

5
@ रॉबर्ट: यह कहकर अधिक सटीक रूप से संक्षेप में प्रस्तुत किया जा सकता है कि एक अभिव्यक्ति एक कवक के लिए है जो एक स्ट्रिंगर के लिए एक स्ट्रिंग है। यह एक स्ट्रिंग / फंक नहीं है, लेकिन ऐसा करने के लिए कहने पर इसे बनाने के लिए आवश्यक डेटा होता है।
फ्लटर

336

मैं एक जवाब के लिए noobs जोड़ रहा हूँ क्योंकि ये जवाब मेरे सिर पर लग रहा था, जब तक मुझे एहसास नहीं हुआ कि यह कितना सरल है। कभी-कभी यह आपकी अपेक्षा है कि यह जटिल है जो आपको 'इसके चारों ओर अपना सिर लपेटने' में असमर्थ बनाता है।

मुझे तब तक अंतर को समझने की ज़रूरत नहीं थी जब तक कि मैं वास्तव में कष्टप्रद 'बग' में लिंकन-टू-एसक्यूएल का उपयोग करने की कोशिश नहीं करता।

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

यह बड़ा काम किया जब तक मुझे बड़े डेटासेट पर OutofMemoryException मिलना शुरू नहीं हुआ। लैम्ब्डा के अंदर ब्रेकपॉइंट्स सेट करने से मुझे एहसास हुआ कि यह मेरी तालिका में प्रत्येक पंक्ति के माध्यम से एक-एक करके मेरी लैम्ब्डा स्थिति के लिए मैचों की तलाश कर रहा था। इसने मुझे थोड़ी देर के लिए रोक दिया, क्योंकि बिल्ली क्यों यह मेरे डेटा टेबल को एक विशाल IEnumerable के रूप में समझ रही है कि वह लिनक्यू-टू-एसक्यूएल करने के बजाय ऐसा कर रहा है जैसे यह माना जाता है? यह मेरे LINQ-to-MongoDb समकक्ष में भी ठीक यही काम कर रहा था।

फिक्स को बस चालू Func<T, bool>करना था Expression<Func<T, bool>>, इसलिए मैंने गुगली की कि यहां समाप्त होने के Expressionबजाय इसकी आवश्यकता क्यों Funcहै।

एक अभिव्यक्ति बस एक प्रतिनिधि को अपने बारे में एक डेटा में बदल देती है। तो a => a + 1कुछ ऐसा हो जाता है "बाईं ओर एक है int a। दाईं ओर आप इसमें 1 जोड़ते हैं।" बस। अब आप घर जा सकते हैं। यह स्पष्ट रूप से उससे अधिक संरचित है, लेकिन यह अनिवार्य रूप से एक अभिव्यक्ति वृक्ष वास्तव में है - अपने सिर को चारों ओर लपेटने के लिए कुछ भी नहीं।

यह समझते हुए, यह स्पष्ट हो जाता है कि LINQ-to-SQL को एक की आवश्यकता क्यों है Expression, और Funcयह पर्याप्त नहीं है। Funcइसे अपने आप में ले जाने का एक तरीका नहीं है, यह देखने के लिए कि इसे SQL / MongoDb / अन्य क्वेरी में कैसे अनुवाद किया जा सकता है। आप देख नहीं सकते कि यह जोड़ या गुणा या घटाव है। बस आप इसे चला सकते हैं। Expressionदूसरी ओर, आपको प्रतिनिधि के अंदर देखने और वह सब कुछ देखने की अनुमति देता है जो वह करना चाहता है। यह आपको एक SQL क्वेरी की तरह, जो भी आप चाहते हैं, प्रतिनिधि को अनुवाद करने का अधिकार देता है। Funcकाम नहीं किया क्योंकि मेरा DbContext लैम्ब्डा अभिव्यक्ति की सामग्री के लिए अंधा था। इस वजह से, यह लैम्ब्डा एक्सप्रेशन को SQL में नहीं बदल सका; हालांकि, इसने अगली सबसे अच्छी बात की और मेरी तालिका में प्रत्येक पंक्ति के माध्यम से उस सशर्त को पुन: प्रसारित किया।

संपादित करें: जॉन पीटर के अनुरोध पर मेरे आखिरी वाक्य पर खुलासा:

IQueryable IEnumerable का विस्तार करता है, इसलिए IEnumerable के तरीकों Where()को स्वीकार करने वाले अधिभार प्राप्त होते हैं Expression। जब आप उसे पास Expressionकरते हैं, तो आप परिणामस्वरूप एक IQueryable रखते हैं, लेकिन जब आप पास होते हैं Func, तो आप वापस IEnumerable आधार पर गिर रहे हैं और आपको परिणामस्वरूप IEnumerable मिलेगा। दूसरे शब्दों में, ध्यान दिए बिना आपने अपने डेटासेट को एक सूची में बदल दिया है, जिसे क्वेरी के लिए किसी चीज़ के विपरीत माना जा सकता है। जब तक आप वास्तव में हस्ताक्षर पर हुड के नीचे नहीं देखते तब तक अंतर नोटिस करना मुश्किल है।


2
चाड; कृपया इस टिप्पणी को थोड़ा और स्पष्ट करें: "फंक ने काम नहीं किया क्योंकि मेरा DbContext SQL में बदलने के लिए लंबोदर अभिव्यक्ति में वास्तव में क्या था, इसलिए यह अगली सबसे अच्छी बात थी और मेरी तालिका में प्रत्येक पंक्ति के माध्यम से उस सशर्त को पुनरावृत्त किया। । "
जॉन पीटर्स 23

2
>> फंक ... यह सब आप इसे चला सकते हैं। यह बिल्कुल सच नहीं है, लेकिन मुझे लगता है कि इस बिंदु पर जोर दिया जाना चाहिए। फ़ंक्स / क्रियाएँ चलाई जानी हैं, अभिव्यक्तियों का विश्लेषण किया जाना है (दौड़ने से पहले या दौड़ने के बजाय भी)।
कॉन्स्टेंटिन

@Chad यहाँ समस्या यह थी ?: db.Set <T> ने सभी डेटाबेस तालिका को क्वियर किया, और उसके बाद, क्योंकि। (कंडीशनलम्बडा) ने कहाँ (IEnumerable) एक्सटेंशन विधि का उपयोग किया था, जो मेमोरी में पूरे टेबल पर गणना की जाती है। । मुझे लगता है कि आपको OutOfMemoryException मिलती है क्योंकि, इस कोड ने मेमोरी को पूरी टेबल लोड करने की कोशिश की (और निश्चित रूप से ऑब्जेक्ट्स बनाए गए)। क्या मैं सही हू? धन्यवाद :)
बेन्स वेर्ट

104

एक्सप्रेशन बनाम फंक की पसंद में एक अत्यंत महत्वपूर्ण विचार यह है कि LINQ जैसे Entities के लिए IQueryable प्रोवाइडर जो आप एक्सप्रेशन में पास करते हैं, उसे pass पचा ’सकते हैं, लेकिन फनक में आप जो पास करते हैं, उसे अनदेखा कर देंगे। मेरे पास इस विषय पर दो ब्लॉग पोस्ट हैं:

LINQ के साथ एंटिटी फ्रेमवर्क और फॉलिंग इन लव के साथ एक्सप्रेशन बनाम फंक पर अधिक - भाग 7: एक्सप्रेशंस और फंक (अंतिम बार)


स्पष्टीकरण के लिए + एल। हालाँकि मुझे 'LINQ एक्सप्रेशन नोड' टाइप 'Invoke' LINQ एंटिटीज में सपोर्ट नहीं है। ' और परिणाम प्राप्त करने के बाद ForEach का उपयोग करना था।
tymtam

77

मैं के बीच मतभेद के बारे में कुछ नोट्स जोड़ना चाहते हैं Func<T>और Expression<Func<T>>:

  • Func<T> सिर्फ एक सामान्य पुराना स्कूल है मल्टीकास्टडेलगेट;
  • Expression<Func<T>> अभिव्यक्ति वृक्ष के रूप में लैम्ब्डा अभिव्यक्ति का प्रतिनिधित्व है;
  • अभिव्यक्ति ट्री का निर्माण लंबोदर अभिव्यक्ति सिंटैक्स या एपीआई सिंटैक्स के माध्यम से किया जा सकता है;
  • अभिव्यक्ति के पेड़ को एक प्रतिनिधि के लिए संकलित किया जा सकता है Func<T>;
  • उलटा रूपांतरण सैद्धांतिक रूप से संभव है, लेकिन यह एक प्रकार का विघटन है, इसके लिए कोई अंतर्निहित कार्यक्षमता नहीं है क्योंकि यह एक सीधी प्रक्रिया नहीं है;
  • अभिव्यक्ति के पेड़ के माध्यम से देखा / अनुवादित / संशोधित किया जा सकता है ExpressionVisitor;
  • IEnumerable के साथ विस्तार के तरीकों के साथ काम Func<T>;
  • IQueryable के लिए विस्तार विधियों के साथ काम करते हैं Expression<Func<T>>

एक लेख है जो कोड नमूनों के साथ विवरण का वर्णन करता है:
LINQ: फंक <टी> बनाम अभिव्यक्ति <फंक <टी >>

आशा है कि यह मददगार होगा।


अच्छी सूची, एक छोटा नोट आप उल्लेख कर रहे हैं कि उलटा रूपांतरण संभव है, हालांकि एक सटीक उलटा नहीं है। रूपांतरण प्रक्रिया के दौरान कुछ मेटाडेटा खो जाता है। हालाँकि आप इसे फिर से संकलित होने पर एक ही परिणाम उत्पन्न करने वाले एक्सप्रेशन ट्री पर विघटित कर सकते हैं।
ऐदीकापी १३'१५ को

76

Krzysztof Cwalina की पुस्तक ( फ्रेमवर्क डिज़ाइन गाइडलाइन्स: कन्वेंशन, मुहावरे और पुन: प्रयोज्य .NET पुस्तकालयों के लिए पैटर्न ) से इसके बारे में अधिक दार्शनिक व्याख्या है ;

रिको मारियानी

गैर-छवि संस्करण के लिए संपादित करें:

अधिकांश बार आप फंक या एक्शन चाहते हैं, अगर ऐसा करने की आवश्यकता है तो कुछ कोड चलाने के लिए है। कोड के विश्लेषण, क्रमबद्ध या चलने से पहले अनुकूलित किए जाने पर आपको अभिव्यक्ति की आवश्यकता होती है। अभिव्यक्ति कोड के बारे में सोचने के लिए है, फंक / एक्शन इसे चलाने के लिए है।


10
अच्छे से कहा। अर्थात। आपको उस समय अभिव्यक्ति की आवश्यकता होती है जब आप उम्मीद कर रहे हैं कि आपका फंक किसी प्रकार के क्वेरी में परिवर्तित हो जाएगा। अर्थात। आप के database.data.Where(i => i.Id > 0)रूप में निष्पादित किया जाना चाहिए SELECT FROM [data] WHERE [id] > 0। तुम सिर्फ एक समारोह में पारित, तो आप अपने ड्राइवर पर blinders डाल दिया है और सभी यह कर सकते हैं है SELECT *प्रत्येक और फिल्टर के माध्यम से और फिर एक बार यह स्मृति में है कि डेटा के सभी भरी हुई है, पुनरावृति आईडी> 0. अपने रैपिंग के साथ सब कुछ बाहर Funcमें Expressionअधिकार ड्राइवर का विश्लेषण करने के लिए Funcऔर इसे Sql / MongoDb / अन्य क्वेरी में बदल दें।
चाड हेडगॉक

इसलिए जब मैं एक छुट्टी की योजना बना रहा हूं, तो मैं उपयोग करूंगा Expressionलेकिन जब मैं छुट्टी पर रहूंगा तो वह होगा Func/Action;)
गोल्डबिशप

1
@ChadHedgcock यह अंतिम टुकड़ा था जिसकी मुझे ज़रूरत थी। धन्यवाद। मैं कुछ समय से इसे देख रहा था, और यहां आपकी टिप्पणी ने सभी अध्ययनों को क्लिक किया।
जॉनी

37

LINQ कैनोनिकल उदाहरण है (उदाहरण के लिए, डेटाबेस से बात करना), लेकिन वास्तव में, किसी भी समय आप वास्तव में ऐसा करने के बजाय क्या करना है, इसे व्यक्त करने के बारे में अधिक परवाह करते हैं। उदाहरण के लिए, मैं प्रोटॉफ़-नेट के आरपीसी स्टैक में इस दृष्टिकोण का उपयोग करता हूं (कोड-पीढ़ी आदि से बचने के लिए) - इसलिए आप इसके साथ एक विधि कहते हैं:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

यह SomeMethod(और प्रत्येक तर्क का मान) हल करने के लिए अभिव्यक्ति ट्री को डिक्रिप्ट करता है, आरपीसी कॉल करता है, किसी भी ref/ अपडेट को अपडेट करता है out, और रिमोट कॉल से परिणाम लौटाता है। यह अभिव्यक्ति पेड़ के माध्यम से ही संभव है। मैं इसे यहां और कवर करता हूं ।

एक अन्य उदाहरण यह है कि जब आप लैम्बडा संकलित करने के उद्देश्य से अभिव्यक्ति के पेड़ मैन्युअल रूप से बना रहे हैं, जैसा कि सामान्य ऑपरेटर्स कोड द्वारा किया जाता है ।


20

जब आप अपने फ़ंक्शन को डेटा के रूप में और कोड के रूप में व्यवहार करना चाहते हैं, तो आप एक अभिव्यक्ति का उपयोग करेंगे। यदि आप कोड (डेटा के रूप में) में हेरफेर करना चाहते हैं तो आप ऐसा कर सकते हैं। अधिकांश समय यदि आपको अभिव्यक्ति की आवश्यकता नहीं दिखती है, तो आपको संभवतः एक का उपयोग करने की आवश्यकता नहीं है।


19

प्राथमिक कारण यह है कि जब आप कोड को सीधे नहीं चलाना चाहते हैं, बल्कि इसका निरीक्षण करना चाहते हैं। यह किसी भी कारण से हो सकता है:

  • कोड को एक अलग वातावरण में मैप करना (यानी सी # कोड इन एंटिटी फ्रेमवर्क में SQL में)
  • रनटाइम में कोड के कुछ हिस्सों को बदलना (डायनेमिक प्रोग्रामिंग या समतल DRY तकनीक)
  • कोड सत्यापन (स्क्रिप्टिंग का अनुकरण करते समय या विश्लेषण करते समय बहुत उपयोगी)
  • सीरियलाइज़ेशन - अभिव्यक्तियों को आसानी से और सुरक्षित रूप से क्रमबद्ध किया जा सकता है, प्रतिनिधि नहीं कर सकते
  • उन चीज़ों पर ज़ोर से टाइप की गई सुरक्षा जो स्वाभाविक रूप से दृढ़ता से टाइप की हुई नहीं हैं, और कंपाइलर चेक का शोषण करना, भले ही आप रनटाइम में डायनेमिक कॉल कर रहे हों (रेजर के साथ ASP.NET MVC 5 एक अच्छा उदाहरण है)

आप संख्या 5 के बारे में अधिक थोड़ा विस्तार से बता सकते हैं
uowzd01

@ uowzd01 रेजर को देखें - यह इस दृष्टिकोण का बड़े पैमाने पर उपयोग करता है।
लुआं

@ Luaan मैं अभिव्यक्ति क्रमबद्धता की तलाश कर रहा हूं लेकिन सीमित तृतीय पक्ष के उपयोग के बिना कुछ भी नहीं पा रहा हूं। क्या .Net 4.5 का समर्थन अभिव्यक्ति ट्री क्रमांकन है?
vabii

@vabii ऐसा नहीं है कि मुझे पता है - और यह वास्तव में सामान्य मामले के लिए एक अच्छा विचार नहीं होगा। मेरी बात और थी कि आप उन विशिष्ट मामलों के लिए बहुत सरल क्रमबद्धता लिखने में सक्षम थे जिन्हें आप समय से पहले डिज़ाइन किए गए इंटरफेस के खिलाफ समर्थन करना चाहते हैं - मैंने बस कुछ ही बार किया है। सामान्य मामले में, Expressionएक प्रतिनिधि के रूप में अनुक्रमित करना असंभव हो सकता है, क्योंकि किसी भी अभिव्यक्ति में एक मनमाने ढंग से प्रतिनिधि / विधि संदर्भ का आह्वान हो सकता है। "ईज़ी" रिश्तेदार है, ज़ाहिर है।
लुआॅन

15

मुझे अभी तक कोई जवाब नहीं मिला है जिसमें प्रदर्शन का उल्लेख है। पासिंग Func<>में रों Where()या Count()बुरा है। असली बुरा। यदि आप एक का उपयोग करते हैं, Func<>तो इसके IEnumerableबजाय LINQ सामान को कॉल करता है IQueryable, जिसका अर्थ है कि पूरी तालिकाओं को अंदर खींच लिया जाता है और फिर फ़िल्टर किया जाता है। Expression<Func<>>यदि आप किसी ऐसे डेटाबेस को क्वेरी कर रहे हैं जो किसी अन्य सर्वर पर रहता है, तो विशेष रूप से तेजी से महत्वपूर्ण है।


क्या यह इन-मेमोरी क्वेरी पर भी लागू होता है?
stt106

@ stt106 शायद नहीं।
mhenry1384

यह केवल तभी सच होता है जब आप सूची की गणना करते हैं। यदि आप GetEnumerator या foreach का उपयोग करते हैं तो आप ienumerable को पूरी तरह से मेमोरी में लोड नहीं करेंगे।
nelsontruran

1
@ stt106 जब .Where () एक सूची के खंड (>), अभिव्यक्ति <Func <> हो जाता है .Compile () उस पर कॉल किया जाता है, तो Func <> लगभग निश्चित रूप से तेज़ है। संदर्भ
।microsoft.com
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.