329

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

これは現在、「式の型を変換できません」というエラーをスローします。ただし、このメソッドが type を返す場合IEnumerable<Object>、コードはビルドされますが、出力で間違った型が返されます。

yield return再帰を使用しながら使用する方法はありますか?

4

8 に答える 8

505

を返すメソッド内ではIEnumerable<T>、ではなくyield returnを返す必要があります。TIEnumerable<T>

交換

yield return c.GetDeepControlsByType<T>();

と:

foreach (var x in c.GetDeepControlsByType<T>())
{
  yield return x;
}
于 2010-01-13T10:29:49.770 に答える
109

再帰呼び出しによって生成された各アイテムを生成する必要があります。

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

このように再帰することにはコストがかかることに注意してください。非常に深いコントロール ツリーがある場合、多くの反復子を作成することになり、パフォーマンスの問題が発生する可能性があります。それを避けたい場合は、基本的に、メソッド内で自分で再帰を実行して、イテレータ (ステート マシン) が 1 つだけ作成されるようにする必要があります。詳細とサンプル実装については、この質問を参照してください。ただし、これにより、明らかにある程度の複雑さも追加されます。

于 2010-01-13T10:29:57.900 に答える
42

Jon Skeet と Colonel 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);
}
于 2015-05-25T15:21:57.253 に答える
20

他の人はあなたに正しい答えを提供しましたが、私はあなたのケースが譲歩することから利益を得ているとは思いません.

これは、譲歩せずに同じことを達成するスニペットです。

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>()));
}
于 2013-08-11T14:38:29.747 に答える
13

2番目に、列挙子自体ではなく、列挙子からアイテムを返す必要があります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;
            }
        }
    }
}
于 2010-01-13T10:30:39.483 に答える
11

列挙型の各コントロールを返す必要があると思います。

    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;
                }
            }
        }
    }
于 2010-01-13T10:32:02.467 に答える
2

そこには多くの良い答えがありますが、LINQメソッドを使用して同じことを達成できることを付け加えておきます.

たとえば、OP の元のコードは次のように書き換えることができます。

public static IEnumerable<Control> 
                           GetDeepControlsByType<T>(this Control control)
{
   return control.Controls.OfType<T>()
          .Union(control.Controls.SelectMany(c => c.GetDeepControlsByType<T>()));        
}
于 2016-06-21T15:07:20.103 に答える