IEnumerable和recursion使用收益率返回
我有一个IEnumerable<T>
方法用于在WebForms页面中查找控件。
该方法是recursion的,我有一些问题返回我想要的types时yield return
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) { 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>())); }