methods using yield not allowed to call themselves
This could well be a user er开发者_JAVA技巧ror (I'm kinda hoping so). I'm running into a strange case in C# were if I try to make recursive call in a method that uses yield it doesn't seem to be respected (i.e. the call is ignored).
The following program illustrates this:
// node in an n-ary tree
class Node
{
public string Name { get; set; }
public List<Node> ChildNodes { get; set; }
}
class Program
{
// walk tree returning all names
static IEnumerable<string> GetAllNames(IEnumerable<Node> nodes)
{
foreach (var node in nodes)
{
if (node.ChildNodes != null)
{
Console.WriteLine("[Debug] entering recursive case");
// recursive case, yield all child node names
GetAllNames(node.ChildNodes);
}
// yield current name
yield return node.Name;
}
}
static void Main(string[] args)
{
// initalize tree structure
var tree = new List<Node>
{
new Node()
{
Name = "One",
ChildNodes = new List<Node>()
{
new Node() {Name = "Two"},
new Node() {Name = "Three"},
new Node() {Name = "Four"},
}
},
new Node() {Name = "Five"}
};
// try and get all names
var names = GetAllNames(tree);
Console.WriteLine(names.Count());
// prints 2, I would expect it to print 5
}
}
You are making the call but doing nothing with it. You need to actually use the result here
static IEnumerable<string> GetAllNames(IEnumerable<Node> nodes) {
foreach (var node in nodes) {
if (node.ChildNodes != null) {
foreach (var childNode in GetAllNames(node.ChildNodes)) {
yield return childNode;
}
}
yield return node.Name;
}
}
You aren't returning the results of the recursive call.
You need to yield return
each item returned from the call:
foreach(var x in GetAllNames(node.ChildNodes))
yield return x;
This is a very interesting problem that can result in A LOT of resource utilization for arbitrarily-deep structures. I think Mr. Skeet proposed a 'flattening' technique but I don't recall the link. Here's the code we use based on his idea (it's an extension method on IEnumerable):
public static class IEnumerableExtensions
{
/// <summary>
/// Visit each node, then visit any child-list(s) the node maintains
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="subjects">IEnumerable to traverse/></param>
/// <param name="getChildren">Delegate to get T's direct children</param>
public static IEnumerable<T> PreOrder<T>(this IEnumerable<T> subjects, Func<T, IEnumerable<T>> getChildren)
{
if (subjects == null)
yield break;
// Would a DQueue work better here?
// A stack could work but we'd have to REVERSE the order of the subjects and children
var stillToProcess = subjects.ToList();
while (stillToProcess.Any())
{
// First, visit the node
T item = stillToProcess[0];
stillToProcess.RemoveAt(0);
yield return item;
// Queue up any children
if (null != getChildren)
{
var children = getChildren(item);
if (null != children)
stillToProcess.InsertRange(0, children);
}
}
}
}
This avoids recursion and a lot of nested iterators. You would then call it:
// try and get all names
var names = tree.PreOrder<Node>(n => n.ChildNodes);
Now this IS a 'pre-order' where the node name comes first, but you could easily write a post-order if that's what you prefer.
精彩评论