c# - उपज रिटर्न का उपयोग करके IENumerable और रिकर्सन




generics yield (6)

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

विधि रिकर्सिव है और मुझे उस प्रकार की लौटने में कुछ समस्याएं आ रही हैं जब मैं चाहता हूं कि yield return रिकर्सिव कॉल के मूल्य को yield return कर 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 का उपयोग करने का कोई तरीका है?


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

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

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>()));
}

आपको अपनी दूसरी 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;
            }
        }
    }
}

एक विधि के अंदर जो IEnumerable<T> , yield return रिटर्न T को वापस करने के लिए है, एक IEnumerable<T>

बदलने के

yield return c.GetDeepControlsByType<T>();

साथ में:

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

जॉन स्कीट और कर्नल पैनिक अपने उत्तरों में नोट करते हैं, रिकर्सिव विधियों में 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();
        }
    }
}

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

रिकर्सिव चयन का उपयोग करके, ओपी की मूल विधि को इस तरह लिखा जा सकता है:

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

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

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

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

सेरेडिनस्की का वाक्यविन्यास सही है, लेकिन आपको रिकर्सिव फ़ंक्शंस में yield return से बचने के लिए सावधान रहना चाहिए क्योंकि यह स्मृति उपयोग के लिए आपदा है। https://.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





yield