IEnumerable和递归使用yield return

我有一个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>();
            }
        }
    }

这当前会引发“无法转换表达式类型”错误。 但是,如果此方法返回类型IEnumerable<Object> ,则会生成代码,但输出中会返回错误的类型。

有没有一种使用yield return同时也使用递归的方法?


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

更换

yield return c.GetDeepControlsByType<T>();

有:

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

您需要产生递归调用产生的每个项目:

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

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


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

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

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的解决方案不同,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);
}
链接地址: http://www.djcxy.com/p/53709.html

上一篇: IEnumerable and Recursion using yield return

下一篇: What does LINQ return when the results are empty