[C#] 수율 반환을 사용한 IEnumerable 및 Recursion

IEnumerable<T>WebForms 페이지에서 컨트롤을 찾는 데 사용 하는 방법이 있습니다.

이 방법은 재귀 적이며 yield return재귀 호출의 값을 returnig 할 때 원하는 유형을 반환하는 데 문제 가 있습니다.

내 코드는 다음과 같습니다.

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

현재 “표현 유형을 변환 할 수 없습니다”오류가 발생합니다. 그러나이 메소드가 type을 리턴 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);
}


답변

다른 사람들이 당신에게 정답을 제공했지만 귀하의 사례가 양보로부터 이익을 얻지 못한다고 생각합니다.

다음은 양보하지 않고 동일한 것을 달성하는 스 니펫입니다.

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


답변

두 번째로 열거 자 자체가 아닌 열거 자 에서 항목 을 반환해야합니다.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)
    {
        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의 구문 은 정확하지만 yield return재귀 함수는 메모리 사용에있어 재난이므로 피해야 합니다. https://stackoverflow.com/a/3970171/284795를 참조 하십시오. 이 기능은 깊이에 따라 폭발적으로 확장됩니다 (유사한 기능은 내 응용 프로그램에서 메모리의 10 %를 사용했습니다).

간단한 해결책은 하나의 목록을 사용하고 재귀와 함께 전달하는 것입니다 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);
    }
}

또는 스택과 while 루프를 사용하여 재귀 호출을 제거 할 수 있습니다 https://codereview.stackexchange.com/a/5661/754