C # में, अनाम विधि में उपज कथन क्यों नहीं हो सकता है?


87

मैंने सोचा कि ऐसा कुछ करना अच्छा होगा (लैंबडा के साथ यील्ड रिटर्न करते हुए):

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();

    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

हालांकि, मुझे पता चला कि मैं बेनामी विधि में उपज का उपयोग नहीं कर सकता। मैं सोच रहा हूँ क्यों। उपज डॉक्स बस इसे अनुमति नहीं है का कहना है।

चूँकि इसकी अनुमति नहीं थी इसलिए मैंने केवल सूची बनाई और इसमें आइटम जोड़े।


अब जब हम C # 5.0 में अंदर जाने की asyncअनुमति दे सकते हैं await, तो मैं यह जानना चाहूंगा कि वे अभी भी गुमनाम पुनरावृत्तियों को yieldअंदर से लागू क्यों नहीं कर रहे हैं । कम या ज्यादा, यह एक ही राज्य मशीन जनरेटर है।
noseratio

जवाबों:


113

एरिक लिपर्ट ने हाल ही में ब्लॉग पोस्ट की एक श्रृंखला के बारे में लिखा है कि कुछ मामलों में उपज की अनुमति क्यों नहीं है।

EDIT2:

  • भाग 7 (यह एक बाद में पोस्ट किया गया था और विशेष रूप से इस प्रश्न को संबोधित करता है)

शायद आपको इसका जवाब मिल जाएगा ...


EDIT1: यह भाग 5 की टिप्पणियों में समझाया गया है, एरिक ने अभिजीत पटेल की टिप्पणी के जवाब में:

क्यू:

एरिक,

क्या आप इस बात की भी जानकारी दे सकते हैं कि "पैदावार" को एक अनाम विधि या लंबोदर अभिव्यक्ति के अंदर की अनुमति क्यों नहीं है

ए :

अच्छा प्रश्न। मुझे गुमनाम यात्रा करने वाले ब्लॉक पसंद आएंगे। यह पूरी तरह से भयानक होगा कि अपने आप को थोड़ा अनुक्रम जनरेटर बनाने में सक्षम हो, जो स्थानीय चर पर बंद हो। कारण सीधा नहीं है: लाभ लागत से आगे बढ़ना नहीं है। अनुक्रम जनरेटर बनाने की अजीबता वास्तव में चीजों की भव्य योजना में बहुत छोटी है और नाममात्र तरीके अधिकांश परिदृश्यों में काम को अच्छी तरह से करते हैं। तो लाभ यह है कि सम्मोहक नहीं हैं।

लागत बड़ी है। संकलक में Iterator पुनर्लेखन सबसे जटिल परिवर्तन है, और अनाम विधि पुनर्लेखन दूसरा सबसे जटिल है। अनाम विधियाँ अन्य अनाम विधियों के अंदर हो सकती हैं, और अनाम विधियाँ पुनरावृत्तियों को ब्लॉक के अंदर हो सकती हैं। इसलिए, हम जो भी करते हैं सबसे पहले हम सभी अनाम विधियों को फिर से लिखते हैं ताकि वे एक बंद वर्ग के तरीके बन जाएं। यह दूसरी-आखिरी चीज़ है जो कंपाइलर एक विधि के लिए IL को छोड़ने से पहले करता है। एक बार जब यह कदम हो जाता है, तो पुन: लिखने वाला यह मान सकता है कि इट्रेटर ब्लॉक में कोई अनाम विधियाँ नहीं हैं; वे सभी पहले से ही फिर से लिखे जा चुके हैं। इसलिए इट्रेटर रीटराइटर सिर्फ इट्रेटर को फिर से लिखने पर ध्यान केंद्रित कर सकता है, बिना इस चिंता के कि वहां कोई अनारक्षित अनाम विधि हो सकती है।

इसके अलावा, इटोमरर गुमनाम तरीकों के विपरीत, कभी भी "घोंसला" ब्लॉक नहीं करता है। पुनरावृत्त लेखक यह मान सकता है कि सभी पुनरावृत्त ब्लॉक "शीर्ष स्तर" हैं।

यदि अनाम विधियों को पुनरावृत्त ब्लॉक शामिल करने की अनुमति दी जाती है, तो वे दोनों धारणाएं खिड़की से बाहर चली जाती हैं। आपके पास एक इट्रेटर ब्लॉक हो सकता है जिसमें एक अनाम विधि होती है जिसमें एक अनाम विधि शामिल होती है जिसमें एक इट्रेटर ब्लॉक होता है जिसमें एक अनाम विधि होती है, और ... बत्तख। अब हमें एक पुनर्लेखन पास लिखना होगा जो एक ही समय में नेस्टेड इटरेटर ब्लॉक और नेस्टेड अनाम विधियों को संभाल सकता है, हमारे दो सबसे जटिल एल्गोरिदम को एक और अधिक जटिल एल्गोरिदम में विलय कर सकता है। इसे डिजाइन करना, कार्यान्वित करना और परीक्षण करना वास्तव में कठिन होगा। हम ऐसा करने के लिए पर्याप्त स्मार्ट हैं, मुझे यकीन है। हमें यहां एक स्मार्ट टीम मिली है। लेकिन हम "अच्छा है, लेकिन आवश्यक नहीं" सुविधा के लिए उस बड़े बोझ को नहीं लेना चाहते हैं। - एरिक


2
दिलचस्प है, खासकर जब से अब स्थानीय कार्य हैं।
माफिया III

4
मुझे आश्चर्य है कि अगर यह उत्तर पुराना है, क्योंकि यह एक स्थानीय फ़ंक्शन में एक उपज रिटर्न लेगा।
यहोशू

2
@ जोशुआ लेकिन एक स्थानीय फ़ंक्शन एक अनाम विधि के समान नहीं है ... अनाम विधियों में उपज वापसी अभी भी अनुमति नहीं है।
थॉमस लेवेस्क

21

एरिक लिपर्ट ने इट्रेटर ब्लॉकों पर सीमाओं (और उन विकल्पों को प्रभावित करने वाले डिज़ाइन निर्णयों) पर लेखों की एक उत्कृष्ट श्रृंखला लिखी है

विशेष रूप से पुनरावृत्त ब्लॉकों को कुछ परिष्कृत संकलक कोड परिवर्तनों द्वारा कार्यान्वित किया जाता है। ये परिवर्तन उन परिवर्तनों के साथ प्रभावित होंगे जो गुमनाम कार्यों या लंबोदर के अंदर होते हैं जैसे कि कुछ परिस्थितियों में वे दोनों कोड को किसी अन्य निर्माण में बदलने की कोशिश करेंगे जो दूसरे के साथ असंगत था।

परिणामस्वरूप उन्हें बातचीत से मना किया जाता है।

कैसे हुड के नीचे iterator ब्लॉक काम अच्छी तरह से निपटाया जाता है यहाँ

एक असंगतता के सरल उदाहरण के रूप में:

public IList<T> GreaterThan<T>(T t)
{
    IList<T> list = GetList<T>();
    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

कंपाइलर एक साथ इसे कुछ इस तरह बदलना चाहता है:

// inner class
private class Magic
{
    private T t;
    private IList<T> list;
    private Magic(List<T> list, T t) { this.list = list; this.t = t;}

    public IEnumerable<T> DoIt()
    {
        var items = () => {
            foreach (var item in list)
                if (fun.Invoke(item))
                    yield return item;
        }
    }
}

public IList<T> GreaterThan<T>(T t)
{
    var magic = new Magic(GetList<T>(), t)
    var items = magic.DoIt();
    return items.ToList();
}

और एक ही समय में पुनरावृत्त पहलू यह करने की कोशिश कर रहा है कि यह एक छोटी सी मशीन बनाने के लिए काम करता है। कुछ सामान्य उदाहरण निष्पक्ष जांच (पहले मनमाने ढंग से) नेस्टेड क्लोजर () के साथ उचित मात्रा में काम करने के साथ काम कर सकते हैं, फिर यह देखते हुए कि क्या बहुत नीचे के स्तर के परिणामस्वरूप कक्षाएं पुनरावृत्त राज्य मशीनों में बदल सकती हैं।

हालाँकि यह होगा

  1. बहुत सारा काम।
  2. संभवतः सभी मामलों में काम नहीं कर सकते बिना बहुत कम से कम इटरेटर ब्लॉक के पहलू को दक्षता के लिए कुछ परिवर्तन लागू करने से रोकने में सक्षम होने के कारण (जैसे कि स्थानीय चर को पूरी तरह से बंद क्लोजर क्लास के बजाय स्थानीय चर को बढ़ावा देने के लिए)।
    • यदि ओवरलैप की थोड़ी सी भी संभावना नहीं थी, जहां इसे लागू करना असंभव या पर्याप्त रूप से कठिन था, तो इसके परिणामस्वरूप समर्थन मुद्दों की संख्या अधिक होगी, क्योंकि कई उपयोगकर्ताओं पर सूक्ष्म ब्रेकिंग परिवर्तन खो जाएगा।
  3. इसके आसपास बहुत आसानी से काम किया जा सकता है।

आपके उदाहरण में ऐसा है:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    return FindInner(expression).ToList();
}

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();
    foreach (var item in list)
        if (fun.Invoke(item))
            yield return item;
}

2
कंपाइलर नहीं कर सकता इसका कोई स्पष्ट कारण नहीं है, एक बार जब यह सभी क्लोजर को हटा देता है, तो सामान्य पुनरावृत्ति परिवर्तन करें। क्या आप एक ऐसे मामले के बारे में जानते हैं जो वास्तव में कुछ कठिनाई पेश करेगा? Btw, आपकी Magicकक्षा होनी चाहिए Magic<T>
क्वर्टी

3

दुर्भाग्य से मुझे नहीं पता कि उन्होंने इसकी अनुमति क्यों नहीं दी, क्योंकि निश्चित रूप से यह कल्पना करना पूरी तरह से संभव है कि यह कैसे काम करेगा।

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

साथ ही, yieldकंपाइलर मैजिक का उपयोग करके इटरेटर विधियों का भी उपयोग किया जाता है।

मेरा अनुमान है कि इन दोनों में से एक कोड को जादू के दूसरे टुकड़े के लिए गैर-पहचान योग्य बनाता है, और यह कि सी # कंपाइलर के वर्तमान संस्करणों के लिए यह काम करने में समय खर्च नहीं करने का निर्णय लिया गया था। बेशक, यह एक शंकालु विकल्प नहीं हो सकता है, और यह सिर्फ इसलिए काम नहीं करता है क्योंकि किसी ने इसे लागू करने के लिए नहीं सोचा था।

100% सटीक प्रश्न के लिए, मैं आपको Microsoft कनेक्ट साइट का उपयोग करने और एक प्रश्न की रिपोर्ट करने का सुझाव दूंगा, मुझे यकीन है कि आपको बदले में कुछ उपयोगी मिलेगा।


1

मैं यह करूँगा:

IList<T> list = GetList<T>();
var fun = expression.Compile();

return list.Where(item => fun.Invoke(item)).ToList();

बेशक आपको Linq विधि के लिए .NET 3.5 से संदर्भित System.Core.dll की आवश्यकता होगी। और शामिल करें:

using System.Linq;

चीयर्स,

धूर्त


0

शायद यह सिर्फ एक वाक्यविन्यास सीमा है। Visual Basic .NET में, जो C # से बहुत मिलता-जुलता है, लिखने के लिए अजीब होता है

Sub Main()
    Console.Write("x: ")
    Dim x = CInt(Console.ReadLine())
    For Each elem In Iterator Function()
                         Dim i = x
                         Do
                             Yield i
                             i += 1
                             x -= 1
                         Loop Until i = x + 20
                     End Function()
        Console.WriteLine($"{elem} to {x}")
    Next
    Console.ReadKey()
End Sub

कोष्ठक पर भी ध्यान दें ' here; लैम्ब्डा समारोह Iterator Function... End Function रिटर्न एक IEnumerable(Of Integer)लेकिन नहीं है इस तरह के एक वस्तु ही। इसे उस वस्तु को प्राप्त करने के लिए बुलाया जाना चाहिए।

[1] द्वारा परिवर्तित कोड C # 7.3 (CS0149) में त्रुटियां बढ़ाता है:

static void Main()
{
    Console.Write("x: ");
    var x = System.Convert.ToInt32(Console.ReadLine());
    // ERROR: CS0149 - Method name expected 
    foreach (var elem in () =>
    {
        var i = x;
        do
        {
            yield return i;
            i += 1;
            x -= 1;
        }
        while (!i == x + 20);
    }())
        Console.WriteLine($"{elem} to {x}");
    Console.ReadKey();
}

मैं अन्य उत्तरों में दिए गए कारण से दृढ़ता से असहमत हूं कि संकलक को संभालना मुश्किल है। Iterator Function()आप VB.NET उदाहरण में देख विशेष रूप से लैम्ब्डा iterators के लिए बनाया जाता है।

VB में, है Iterator कीवर्ड है; इसका कोई C # प्रतिपक्ष नहीं है। IMHO, कोई वास्तविक कारण नहीं है यह C # की विशेषता नहीं है।

इसलिए यदि आप वास्तव में, वास्तव में अनाम इटैलर फ़ंक्शन चाहते हैं, तो वर्तमान में विज़ुअल बेसिक या (मैंने इसे चेक नहीं किया है) F # का उपयोग करें, जैसा कि @Thomas Levesque के उत्तर में भाग # 7 की टिप्पणी में कहा गया है (F # के लिए Ctrl + F करें)।

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