我有一个 IEnumerable<T>
方法,我用它来在WebForms页面中找到控件 .
该方法是递归的,当 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>();
}
}
}
这当前抛出"Cannot convert expression type"错误 . 但是,如果此方法返回类型 IEnumerable<Object>
,则代码将构建,但输出中将返回错误的类型 .
有没有办法使用 yield return
同时也使用递归?
8 回答
在返回
IEnumerable<T>
的方法内,yield return
必须返回T
,而不是IEnumerable<T>
.更换
有:
您需要生成递归调用产生的每个项目:
请注意,'s a cost to recursing in this way - you'll最终会创建大量迭代器,如果您有一个非常深的控制树,这可能会产生性能问题 . 如果你想避免这种情况,你基本上需要在方法中自己进行递归,以确保只创建了一个迭代器(状态机) . 有关更多详细信息和示例实现,请参阅this question - 但这显然也增加了一定的复杂性 .
正如Jon Skeet和Panic上校在他们的回答中指出的那样,如果树很深,在递归方法中使用_1858409可能会导致性能问题 .
这是一个通用的非递归扩展方法,它执行一系列树的深度优先遍历:
与Eric Lippert's solution不同,RecursiveSelect直接与枚举器一起工作,因此它不需要调用Reverse(它将整个序列缓存在内存中) .
使用RecursiveSelect,可以简单地重写OP的原始方法,如下所示:
其他人为您提供了正确的答案,但我不认为您的案件会从屈服中受益 .
这是一个实现同样而不屈服的片段 .
您需要在第二个
yield return
中返回枚举器中的项目,而不是枚举器本身 .我认为你必须返回枚举中的每个控件 .
Seredynski's syntax是正确的,但是你应该小心避免在递归函数中使用
yield return
,因为它是内存使用的灾难 . 请参阅https://stackoverflow.com/a/3970171/284795它随着深度爆炸性地扩展(类似的功能在我的应用中使用了10%的内存) .一个简单的解决方案是使用一个列表并将其传递给递归https://codereview.stackexchange.com/a/5651/754
或者你可以使用堆栈和while循环来消除递归调用https://codereview.stackexchange.com/a/5661/754
虽然有很多好的答案,但我仍然会补充说可以使用LINQ方法来完成同样的事情 .
例如,OP的原始代码可以重写为: