IEnumerable and Recursion using yield return
I have an IEnumerable<T>
method that I'm using to find controls in a WebForms page.
The method is recursive and I'm having some problems returning the type I want when the yield return
is returnig the value of the recursive call.
My code looks as follows:
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>();
}
}
}
This currently throws a "Cannot convert expression type" error. If however this method returns type IEnumerable<Object>
, the code builds, but the wrong type is returned in the output.
Is there a way of using yield return
whilst also using recursion?
Inside a method that returns IEnumerable<T>
, yield return
has to return T
, not an IEnumerable<T>
.
Replace
yield return c.GetDeepControlsByType<T>();
with:
foreach (var x in c.GetDeepControlsByType<T>())
{
yield return x;
}
You need to yield each of the items yielded by the recursive call:
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;
}
}
}
}
Note that there's a cost to recursing in this way - you'll end up creating a lot of iterators, which can create a performance issue if you have a really deep control tree. If you want to avoid that, you basically need to do the recursion yourself within the method, to make sure there's only one iterator (state machine) created. See this question for more details and a sample implementation - but this obviously adds a certain amount of complexity too.
As Jon Skeet and Colonel Panic note in their answers, using yield return
in recursive methods may cause performance problems if the tree is very deep.
Here's a generic non-recursive extension method that performs a depth-first traversal of a sequence of trees:
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();
}
}
}
Unlike Eric Lippert's solution, RecursiveSelect works directly with enumerators so that it doesn't need to call Reverse (which buffers the entire sequence in memory).
Using RecursiveSelect, the OP's original method can be rewritten simply like this:
public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
return control.Controls.RecursiveSelect(c => c.Controls).Where(c => c is T);
}
链接地址: http://www.djcxy.com/p/53710.html
上一篇: 产量回报如何在c#中工作