C# IEnumerable 和 Recursion 使用 yield return
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2055927/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
IEnumerable and Recursion using yield return
提问by Jamie Dixon
I have an IEnumerable<T>
method that I'm using to find controls in a WebForms page.
我有一种IEnumerable<T>
方法可用于在 WebForms 页面中查找控件。
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.
该方法是递归的,当返回yield return
递归调用的值时,我在返回我想要的类型时遇到了一些问题。
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.
这当前会引发“无法转换表达式类型”错误。但是IEnumerable<Object>
,如果此方法返回 type ,则代码会生成,但输出中会返回错误的类型。
Is there a way of using yield return
whilst also using recursion?
有没有办法yield return
同时使用递归?
采纳答案by Marcin Seredynski
Inside a method that returns IEnumerable<T>
, yield return
has to return T
, not an IEnumerable<T>
.
在返回的方法中IEnumerable<T>
,yield return
必须返回T
,而不是IEnumerable<T>
。
Replace
代替
yield return c.GetDeepControlsByType<T>();
with:
和:
foreach (var x in c.GetDeepControlsByType<T>())
{
yield return x;
}
回答by Jon Skeet
You need to yield each of the itemsyielded 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 questionfor more details and a sample implementation - but this obviously adds a certain amount of complexity too.
请注意,以这种方式递归是有代价的——你最终会创建很多迭代器,如果你有一个非常深的控制树,这可能会产生性能问题。如果你想避免这种情况,你基本上需要在方法中自己进行递归,以确保只创建一个迭代器(状态机)。有关更多详细信息和示例实现,请参阅此问题- 但这显然也增加了一定的复杂性。
回答by Rob Levine
You need to return the itemsfrom the enumerator, not the enumerator itself, in your second 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)
{
foreach (Control ctrl in c.GetDeepControlsByType<T>())
{
yield return ctrl;
}
}
}
}
回答by Torbj?rn Hansson
I think you have to yield return each of the controls in the enumerables.
我认为你必须让枚举中的每个控件都返回。
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;
}
}
}
}
回答by tymtam
Others provided you with the correct answer, but I don't think your case benefits from yielding.
其他人为您提供了正确的答案,但我认为您的案例不会从屈服中受益。
Here's a snippet which achieves the same without yielding.
这是一个片段,它在不屈服的情况下实现了相同的目标。
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>()));
}
回答by Colonel Panic
Seredynski's syntaxis correct, but you should be careful to avoid yield return
in recursive functions because it's a disaster for memory usage. See https://stackoverflow.com/a/3970171/284795it scales explosively with depth (a similar function was using 10% of memory in my app).
Seredynski 的语法是正确的,但您应该小心避免yield return
在递归函数中使用,因为它对内存使用来说是一场灾难。请参阅https://stackoverflow.com/a/3970171/284795它随深度爆炸性扩展(类似的功能在我的应用程序中使用了 10% 的内存)。
A simple solution is to use one list and pass it with the recursion https://codereview.stackexchange.com/a/5651/754
一个简单的解决方案是使用一个列表并将其与递归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);
}
}
Alternatively you could use a stack and a while loop to eliminate recursive calls https://codereview.stackexchange.com/a/5661/754
或者,您可以使用堆栈和 while 循环来消除递归调用 https://codereview.stackexchange.com/a/5661/754
回答by Michael Liu
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.
正如 Jon Skeet 和 Colonel Panic 在他们的回答中指出的那样,yield return
如果树很深,在递归方法中使用可能会导致性能问题。
Here's a generic non-recursiveextension 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).
与Eric Lippert 的解决方案不同,RecursiveSelect 直接与枚举器一起工作,因此它不需要调用 Reverse(它在内存中缓冲整个序列)。
Using RecursiveSelect, the OP's original method can be rewritten simply like this:
使用 RecursiveSelect,可以像这样简单地重写 OP 的原始方法:
public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
return control.Controls.RecursiveSelect(c => c.Controls).Where(c => c is T);
}
回答by yoel halb
While there are many good answers out there, I would still add that it is possible to use LINQ methods to accomplish the same thing, .
虽然那里有很多好的答案,但我仍然补充说,可以使用 LINQ 方法来完成同样的事情,.
For instance, the original code of the OP could be rewritten as:
例如,OP 的原始代码可以重写为:
public static IEnumerable<Control>
GetDeepControlsByType<T>(this Control control)
{
return control.Controls.OfType<T>()
.Union(control.Controls.SelectMany(c => c.GetDeepControlsByType<T>()));
}