IEnumerable和recursion使用收益率返回

我有一个IEnumerable<T>方法用于在WebForms页面中查找控件。

该方法是recursion的,我有一些问题返回我想要的types时yield returnrecursion调用的价值。

我的代码如下所示:

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

这当前抛出一个“不能转换expression式types”的错误。 但是,如果此方法返回typesIEnumerable<Object> ,则会生成代码,但输出中将返回错误的types。

有没有一种方法使用recursion,同时也使用recursion?

在返回IEnumerable<T>的方法中, yield return必须返回T ,而不是IEnumerable<T>

更换

 yield return c.GetDeepControlsByType<T>(); 

有:

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

您需要产生recursion调用产生的每个项目

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

请注意,以这种方式recursion是需要花费的 – 最终会创build大量的迭代器,如果您拥有非常深的控制树,则可能会产生性能问题。 如果你想避免这种情况,你基本上需要在方法中自己做recursion,以确保只有一个迭代器(状态机)被创build。 看到这个问题的更多细节和示例实现 – 但这显然增加了一定的复杂性。

Jon Skeet和Panic上校在答复中指出,如果树很深,在recursion方法中使用yield return可能会导致性能问题。

下面是一个通用的非recursion扩展方法,它执行一系列树的深度优先遍历:

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

与Eric Lippert的解决scheme不同,RecursiveSelect直接与枚举器一起工作,因此不需要调用Reverse(缓冲整个内存序列)。

使用RecursiveSelect,OP的原始方法可以像这样重写:

 public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control) { return control.Controls.RecursiveSelect(c => c.Controls).Where(c => c is 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; } } } } 

其他人提供了正确的答案,但我不认为你的情况从收益中受益。

这里有一个片段,而不会产生相同的结果。

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

我想你必须让枚举中的每一个控件都返回。

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

Seredynski的语法是正确的,但是您应该小心避免recursion函数中的yield return ,因为这是内存使用的灾难。 请参阅https://stackoverflow.com/a/3970171/284795它具有深度的爆炸式扩展(类似的function是使用我的应用程序的10%的内存)。;

一个简单的解决scheme是使用一个列表,并通过recursionhttps://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); } } 

或者,您可以使用堆栈和while循环来消除recursion调用https://codereview.stackexchange.com/a/5661/754

虽然有很多很好的答案,但我仍然会补充说,可以使用LINQ方法来完成同样的事情。

例如,OP的原始代码可以被重写为:

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