C# の yield return で再帰呼び出しを行うには
今回は C# の yield return
文を再帰メソッドの中で用いるときの書き方をメモしておきます。
概要
C# には yield return
という構文があります。これは IEnumerable<T>
を返すようなメソッドの中に記述し、 foreach
などの呼び出し元に対して、コレクション要素を返すものです。
IEnumerable<int> GetNumber()
{
yield return 1;
yield return 2;
yield return 3;
}
たとえば上の GetNumber
メソッドを foreach
で使うことができます。
// 1, 2, 3 を順に出力
foreach (var n in GetNumber())
{
Console.WriteLine(n);
}
GetNumber
内で List<T>
などに格納して返してもいいのですが、その場合すべての要素を列挙し終えるまで GetNumber
を抜けないため、要素数が多い場合などは yield return
のほうが望ましいでしょう。
まぁ、詳細な説明はグーグル先生にお任せするとして、今回は再帰メソッドでの使い方をご紹介します。
再帰呼び出しでの yield return
今回の例は一つの XmlElement
インスタンスを渡して、自分を含む配下のすべての XmlElement
を返すメソッドです。
素直に再帰メソッドを考えれば下記のように書きたくなります。
private static IEnumerable<XmlElement> GetXmlElements(XmlElement node)
{
yield return node; // とりあえず自分を返す
if (!node.HasChildNodes) yield break; // 子ノードがなければ抜ける
foreach (XmlNode child in node.ChildNodes)
{
if (child.NodeType == XmlNodeType.Element) // 子ノードが XmlElement なら
{
return GetXmlElements(child as XmlElement); // ここが再帰呼び出し!でも動かない。
}
}
}
yield return
を使うメソッドで強制的に抜けたい場合は yield break
を使いましょう。このへんはコレクションらしいですね。
しかしこのコードは return GetXmlElements(child as XmlElement);
の部分がコンパイルできません。 yield return
を使用しているメソッド中では return
が書けないからです。
かといって return
しなければ、子ノードに対する呼び出しの結果は返されないまま終わってしまいます。
ということでどうするかと言うと、再帰呼び出しで戻ってくるコレクションをさらに foreach
と yield return
で戻します。
書き換えると下記のようになります。
private static IEnumerable<XmlElement> GetXmlElements(XmlElement node)
{
yield return node; // とりあえず自分を返す
if (!node.HasChildNodes) yield break; // 子ノードがなければ抜ける
foreach (XmlNode child in node.ChildNodes)
{
if (child.NodeType == XmlNodeType.Element) // 子ノードが XmlElement なら
{
var subElements = GetXmlElements(child as XmlElement); // 再帰呼び出し
foreach (var subElement in subElements)
{
yield return subElement; // 子ノードと含まれる要素を返す
}
}
}
}
少々ややこしいですが、読めないほどではありませんね。
あとがき
ツリー構造を走査するときに便利な再帰呼び出し。yield return
を使ってリッチに書いてみてはいかがでしょう?