.net फंक <T> को .net एक्सप्रेशन <फंक <टी >> में कनवर्ट करना


118

एक मेम्ने से एक एक्सप्रेशन पर जाना एक विधि कॉल का उपयोग करना आसान है ...

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}

लेकिन मैं फंक को एक अभिव्यक्ति में बदलना चाहता हूं, केवल दुर्लभ मामलों में ...

public void ContainTheDanger(Func<T> dangerousCall)
{
    try 
    {
        dangerousCall();
    }
    catch (Exception e)
    {
        // This next line does not work...
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

जो लाइन काम नहीं करती है वह मुझे कंपाइल-टाइम एरर देती है Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'। एक स्पष्ट कलाकार स्थिति का समाधान नहीं करता है। क्या ऐसा करने के लिए कोई सुविधा है जो मैं देख रहा हूं?


मैं वास्तव में 'दुर्लभ मामले' उदाहरण के लिए ज्यादा उपयोग नहीं देखता। कॉलर फंक <T> में गुजर रहा है। कॉलर को वापस दोहराने की कोई आवश्यकता नहीं है कि फंक <T> (अपवाद के माध्यम से) क्या था।
एडम राल्फ

2
अपवाद कॉलर में नहीं संभाला जाता है। और, क्योंकि फुक <टी> एस में कई कॉल साइटें गुजर रही हैं, इसलिए कॉल करने वाले में अपवाद को पकड़ने से दोहराव पैदा होता है।
डेव कैमरून

1
अपवाद स्टैक ट्रेस इस जानकारी को दिखाने के लिए डिज़ाइन किया गया है। यदि फंक <टी> के आह्वान के भीतर अपवाद को फेंक दिया जाता है, तो यह स्टैक ट्रेस में दिखाई देगा। संयोग से, यदि आप दूसरे रास्ते पर जाने का चयन करने वाले थे, अर्थात एक अभिव्यक्ति को स्वीकार करते हैं और इसे आह्वान के लिए संकलित करते हैं, तो आप इसे खो देंगे क्योंकि स्टैक ट्रेस को at lambda_method(Closure )संकलित प्रतिनिधि के आह्वान के लिए कुछ दिखाई देगा ।
एडम राल्फ

मुझे लगता है कि आपको इस [लिंक] [1] [1] में जवाब देखना चाहिए: stackoverflow.com/questions/9377635/create-expression-from-func/…
इब्राहिम कैस इब्राहिम

जवाबों:


104

ऊह, यह आसान नहीं है। Func<T>एक सामान्य delegateऔर एक अभिव्यक्ति का प्रतिनिधित्व करता है । यदि कोई ऐसा तरीका है जो आप ऐसा कर सकते हैं (संकलक द्वारा किए गए अनुकूलन और अन्य चीजों के कारण, कुछ डेटा को फेंक दिया जा सकता है, तो मूल अभिव्यक्ति को वापस पाना असंभव हो सकता है), यह मक्खी पर आईएल को नष्ट करना होगा। और अभिव्यक्ति का जिक्र करना (जो किसी भी तरह से आसान नहीं है)। लैम्ब्डा एक्सप्रेशन को डेटा के रूप में मानना ​​( Expression<Func<T>>) कंपाइलर द्वारा किया गया जादू है (मूल रूप से कंपाइलर IL में कंपाइल करने के बजाय कोड में एक एक्सप्रेशन ट्री बनाता है)।

संबंधित तथ्य

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


3
लिस्प की व्याख्या नहीं करनी है, इसे आसानी से संकलित किया जा सकता है। मैक्रोज़ को संकलन समय पर विस्तारित किया जाना चाहिए, और यदि आप समर्थन करना चाहते हैं तो आपको evalकंपाइलर शुरू करने की आवश्यकता होगी, लेकिन इसके अलावा, ऐसा करने में कोई समस्या नहीं है।
विन्यासकर्ता

2
"एक्सप्रेशन <फंक <टी >> डेंजरस एक्सप्रेशन = () => खतरनाककॉल ();" आसान नहीं है?
माहेमान

10
@ माहेमान जो Expressionआपके रैपर एक्शन के बारे में नया निर्माण करेगा , लेकिन इसमें dangerousCallप्रतिनिधि के इंटर्नल के बारे में कोई अभिव्यक्ति ट्री जानकारी नहीं होगी ।
नेनाद

34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 

1
मैं लौटे अभिव्यक्ति के वाक्यविन्यास पेड़ को पार करना चाहता था। क्या यह दृष्टिकोण मुझे ऐसा करने की अनुमति देगा?
डेव कैमरून

6
@DaveCameron - नहीं, ऊपर दिए गए उत्तर देखें - पहले से संकलित Funcएक नई अभिव्यक्ति में छिपा होगा। यह केवल कोड पर डेटा की एक परत जोड़ता है; आप fआगे की जानकारी के बिना अपने पैरामीटर को खोजने के लिए एक परत को पार कर सकते हैं , इसलिए आप वहीं हैं जहां आपने शुरू किया था।
जोनो

21

आपको शायद क्या करना चाहिए, विधि को चारों ओर घुमाएं। एक एक्सप्रेशन> में लें, और संकलित करें और चलाएं। यदि यह विफल रहता है, तो आपके पास पहले से ही देखने के लिए अभिव्यक्ति है।

public void ContainTheDanger(Expression<Func<T>> dangerousCall)
{
    try 
    {
        dangerousCall().Compile().Invoke();;
    }
    catch (Exception e)
    {
        // This next line does not work...
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

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


7

आप हालांकि .Compile () विधि के माध्यम से अन्य तरीके से जा सकते हैं - सुनिश्चित नहीं हैं कि यह आपके लिए उपयोगी है:

public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
{
    try
    {
        var expr = dangerousCall.Compile();
        expr.Invoke();
    }
    catch (Exception e)
    {
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
        throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    var thing = new Thing();
    ContainTheDanger(() => thing.CrossTheStreams());
}

6

यदि आपको कभी अभिव्यक्ति की आवश्यकता होती है और कभी-कभी एक प्रतिनिधि की आवश्यकता होती है, तो आपके पास 2 विकल्प हैं:

  • अलग-अलग विधियाँ हैं (प्रत्येक के लिए 1)
  • हमेशा Expression<...>संस्करण को स्वीकार करें , और बस .Compile().Invoke(...)अगर आप एक प्रतिनिधि चाहते हैं। जाहिर है कि यह लागत है।

6

NJection.LambdaConverter एक पुस्तकालय है जो प्रतिनिधियों को अभिव्यक्ति में परिवर्तित करता है

public class Program
{
    private static void Main(string[] args) {
       var lambda = Lambda.TransformMethodTo<Func<string, int>>()
                          .From(() => Parse)
                          .ToLambda();            
    }   

    public static int Parse(string value) {
       return int.Parse(value)
    } 
}

4
 Expression<Func<T>> ToExpression<T>(Func<T> call)
        {
            MethodCallExpression methodCall = call.Target == null
                ? Expression.Call(call.Method)
                : Expression.Call(Expression.Constant(call.Target), call.Method);

            return Expression.Lambda<Func<T>>(methodCall);
        }

क्या आप "यह काम नहीं करेगा" भाग को विस्तृत कर सकते हैं? क्या आपने वास्तव में इसे संकलित और क्रियान्वित करने की कोशिश की है? या यह आपके आवेदन में विशेष रूप से काम नहीं करता है?
दिमित्री डेज़ीगिन

1
एफडब्ल्यूआईडब्ल्यू, यह वह नहीं हो सकता है जो मुख्य टिकट के बारे में था, लेकिन यह वही था जो मुझे चाहिए था। यह वह call.Targetहिस्सा था जो मुझे मार रहा था। इसने वर्षों तक काम किया, और फिर अचानक काम करना बंद कर दिया और स्थैतिक / गैर-स्थिर ब्लाह ब्लाह के बारे में शिकायत करने लगा। कोई बात नहीं धन्यवाद!
एली गैसर्ट


-1

परिवर्तन

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

सेवा

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();

सर्व, यह अभिव्यक्ति पाने के लिए बिल्कुल कानूनी तरीका है। सिंटेक्स शुगर हालांकि इसे बनाने के लिए। आपको क्या लगता है कि इसे रनटाइम में विफल होना चाहिए?
रोमन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.