उपज वापसी का उपयोग करते हुए IEnumerable और Recursion


307

मेरे पास है एक IEnumerable<T> तरीका है जो मैं WebForms पेज में नियंत्रण खोजने के लिए उपयोग कर रहा हूं।

विधि पुनरावर्ती है और मुझे कुछ प्रकार की समस्याएं आ रही हैं जो मैं चाहता हूं कि जब मैं yield returnपुनरावर्ती कॉल का मूल्य वापस कर रहा हूं ।

मेरा कोड इस प्रकार दिखता है:

    public static IEnumerable<Control> 
                               GetDeepControlsByType<T>(this Control control)
    {
        foreach(Control c in control.Controls)
        {
            if (c is T)
            {
                yield return c;
            }

            if(c.Controls.Count > 0)
            {
                yield return c.GetDeepControlsByType<T>();
            }
        }
    }

यह वर्तमान में "अभिव्यक्ति प्रकार परिवर्तित नहीं कर सकता है" त्रुटि फेंकता है। यदि यह विधि वापस आती है IEnumerable<Object>, तो कोड बनाता है, लेकिन आउटपुट में गलत प्रकार वापस आ जाता है।

वहाँ yield returnभी पुनरावृत्ति का उपयोग कर whilst का उपयोग करने का एक तरीका है ?


1
stackoverflow.com/questions/1815497/… : धागे पर "mrydengrens" उत्तर के लिए लिंक "इनकमिंग कलेक्टिंग जो स्वाभाविक रूप से IEnumerable नहीं हैं?" उसका नमूना कोड एरिक लिपर्ट के एक ब्लॉग के लेख पर आधारित है, जिसमें दिखाया गया है कि कैसे आप लिनक के साथ पुनरावर्ती गणन में ढेर का उपयोग करते हैं, इस प्रकार पुनरावृत्तियों द्वारा संभवतः महंगे स्मृति उपयोग से बचते हैं। बहुत उपयोगी है!
बिलवड

Btw। if(c.Controls.Count > 0)-> if(c.Controls.Any()), खासकर यदि आप के रूप में अच्छी तरह से उपज रहे हैं :)
tymtam

मुझे नहीं लगता कि इस मामले से लाभ मिलता है। पूर्णता के लिए मैंने बिना किसी कार्यान्वयन के प्रदान किया yield। कृपया नीचे देखें :) और यह वन-लाइनर भी है :)
tymtam

आपको yield returnपुनरावर्ती कार्यों से बचने के लिए सावधान रहना चाहिए , मेमोरी उपयोग विस्फोटक रूप से होता है। देखें stackoverflow.com/a/30300257/284795
कर्नल पैनिक

जवाबों:


485

एक विधि के भीतर जो लौटता है IEnumerable<T>, yield returnलौटना पड़ता है T, नहीं IEnumerable<T>

बदलने के

yield return c.GetDeepControlsByType<T>();

साथ में:

foreach (var x in c.GetDeepControlsByType<T>())
{
  yield return x;
}

98

आपको पुनरावर्ती कॉल द्वारा प्राप्त की गई प्रत्येक आइटम को प्राप्त करने की आवश्यकता है :

public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
    foreach(Control c in control.Controls)
    {
        if (c is T)
        {
            yield return c;
        }

        if(c.Controls.Count > 0)
        {
            foreach (Control control in c.GetDeepControlsByType<T>())
            {
                yield return control;
            }
        }
    }
}

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


2
मैं इसके बारे में उपज जॉन का उल्लेख नहीं किया गया है एक सूत्र में आश्चर्य की बात है कि लगता है c.Controls.Count > 0बनाम .Any():)
tymtam

@Tymek वास्तव में यह जुड़ा हुआ जवाब में उल्लेख किया गया है।

28

जैसा कि जॉन स्केट और कर्नल पैनिक ने अपने जवाब में कहा, yield returnपुनरावर्ती तरीकों का उपयोग करने से पेड़ को बहुत गहरा होने पर प्रदर्शन की समस्या हो सकती है।

यहां एक सामान्य गैर-पुनरावर्ती विस्तार विधि है जो पेड़ों के अनुक्रम के गहराई-पहले ट्रैवर्सल का प्रदर्शन करती है:

public static IEnumerable<TSource> RecursiveSelect<TSource>(
    this IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> childSelector)
{
    var stack = new Stack<IEnumerator<TSource>>();
    var enumerator = source.GetEnumerator();

    try
    {
        while (true)
        {
            if (enumerator.MoveNext())
            {
                TSource element = enumerator.Current;
                yield return element;

                stack.Push(enumerator);
                enumerator = childSelector(element).GetEnumerator();
            }
            else if (stack.Count > 0)
            {
                enumerator.Dispose();
                enumerator = stack.Pop();
            }
            else
            {
                yield break;
            }
        }
    }
    finally
    {
        enumerator.Dispose();

        while (stack.Count > 0) // Clean up in case of an exception.
        {
            enumerator = stack.Pop();
            enumerator.Dispose();
        }
    }
}

एरिक लिपर्ट के समाधान के विपरीत , RecursiveSelect सीधे एन्यूमरेटर्स के साथ काम करता है, ताकि इसे रिवर्स (जिसे मेमोरी में संपूर्ण अनुक्रम बफ़र्स) कॉल करने की आवश्यकता न हो।

RecursiveSelect का उपयोग करते हुए, ओपी की मूल विधि को इस तरह से फिर से लिखा जा सकता है:

public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
    return control.Controls.RecursiveSelect(c => c.Controls).Where(c => c is T);
}

काम करने के लिए इस (उत्कृष्ट) कोड को प्राप्त करने के लिए, मुझे IEnumable रूप में ControlCollection प्राप्त करने के लिए टाइप का उपयोग करना पड़ा; Windows प्रपत्रों में, एक नियंत्रण नियंत्रण योग्य नहीं है: नियंत्रण लौटाएँ। नियंत्रण चिह्न। नियंत्रण <> );
बिलवेड

17

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

यहाँ एक स्निपेट है जो बिना पैदावार के समान प्राप्त करता है।

public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
   return control.Controls
                 .Where(c => c is T)
                 .Concat(control.Controls
                                .SelectMany(c =>c.GetDeepControlsByType<T>()));
}

2
LINQ का उपयोग नहीं करता है yield? ;)
फिलिप एम।

यह चालाक है। मुझे हमेशा अतिरिक्त foreachपाश से परेशान किया गया है। अब मैं शुद्ध कार्यात्मक प्रोग्रामिंग के साथ ऐसा कर सकता हूं!
jsuddsjr

1
मुझे यह समाधान पठनीयता के संदर्भ में पसंद है, लेकिन यह पैदावार का उपयोग करने के रूप में पुनरावृत्तियों के साथ एक ही प्रदर्शन के मुद्दे का सामना करता है। @PhilippM: सत्यापित करें कि LINQ उपज संदर्भों
Herman

एक महान समाधान के लिए अंगूठे।
तोमर डब्ल्यू

12

आपको एन्यूमरेटर से आइटम को वापस करने की आवश्यकता है , न कि एन्यूमरेटर को अपने दूसरे मेंyield return

public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
    foreach (Control c in control.Controls)
    {
        if (c is T)
        {
            yield return c;
        }

        if (c.Controls.Count > 0)
        {
            foreach (Control ctrl in c.GetDeepControlsByType<T>())
            {
                yield return ctrl;
            }
        }
    }
}

9

मुझे लगता है कि आपको एनामेराबल्स में प्रत्येक नियंत्रण को वापस करना होगा।

    public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
    {
        foreach (Control c in control.Controls)
        {
            if (c is T)
            {
                yield return c;
            }

            if (c.Controls.Count > 0)
            {
                foreach (Control childControl in c.GetDeepControlsByType<T>())
                {
                    yield return childControl;
                }
            }
        }
    }

8

Seredynski का वाक्यविन्यास सही है, लेकिन आपको yield returnपुनरावर्ती कार्यों से बचने के लिए सावधान रहना चाहिए क्योंकि यह स्मृति उपयोग के लिए एक आपदा है। Https://stackoverflow.com/a/3970171/284795 देखें यह गहराई के साथ विस्फोटक मापता है (एक समान कार्य मेरे एप्लिकेशन में स्मृति का 10% उपयोग कर रहा था)।

एक सरल उपाय यह है कि आप किसी एक सूची का उपयोग करें और इसे पुनरावर्ती https://codereview.stackexchange.com/a/5651/754 के साथ पास करें

/// <summary>
/// Append the descendents of tree to the given list.
/// </summary>
private void AppendDescendents(Tree tree, List<Tree> descendents)
{
    foreach (var child in tree.Children)
    {
        descendents.Add(child);
        AppendDescendents(child, descendents);
    }
}

वैकल्पिक रूप से आप पुनरावर्ती कॉल को समाप्त करने के लिए एक स्टैक और थोड़ी देर के लूप का उपयोग कर सकते हैं https://codereview.stackexchange.com/a/5661/754


0

जबकि वहाँ कई अच्छे उत्तर हैं, मैं अभी भी जोड़ूंगा कि एक ही चीज़ को पूरा करने के लिए LINQ विधियों का उपयोग करना संभव है।

उदाहरण के लिए, ओपी के मूल कोड को फिर से लिखा जा सकता है:

public static IEnumerable<Control> 
                           GetDeepControlsByType<T>(this Control control)
{
   return control.Controls.OfType<T>()
          .Union(control.Controls.SelectMany(c => c.GetDeepControlsByType<T>()));        
}

उसी दृष्टिकोण का उपयोग करते हुए एक समाधान तीन साल पहले पोस्ट किया गया था
सर्वि

@Servy हालांकि यह समान है (जो कि BTW मैं सभी उत्तरों के बीच छूट गया ... इस उत्तर को लिखते समय), यह अभी भी अलग है, क्योंकि यह उपयोग करता है। फ़िल्टर करने के लिए .OfType <> और .Union ()
yoel

2
OfTypeवास्तव में एक meainingful अलग नहीं है। सबसे मामूली शैलीगत परिवर्तन पर। एक नियंत्रण कई नियंत्रणों का बच्चा नहीं हो सकता है, इसलिए ट्रैवर्स किए गए पेड़ पहले से ही अनएक्यू है। Unionइसके बजाय का उपयोग करना Concatअनावश्यक रूप से पहले से ही अद्वितीय होने की गारंटी वाले अनुक्रम की विशिष्टता की पुष्टि करना है, और इसलिए यह एक उद्देश्य अपग्रेड है।
17
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.