методы, использующие yield, не могут называть себя - PullRequest
1 голос
/ 26 сентября 2011

Это вполне может быть ошибка пользователя (я на это надеюсь).Я сталкиваюсь со странным случаем в C #, если я пытаюсь сделать рекурсивный вызов в методе, который использует yield, он, кажется, не соблюдается (то есть вызов игнорируется).

Следующая программа иллюстрируетэто:

// 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

    }
}

Ответы [ 3 ]

3 голосов
/ 26 сентября 2011

Вы делаете звонок, но ничего не делаете с ним. Вам нужно использовать результат здесь

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;
    }
}
2 голосов
/ 26 сентября 2011

Вы не возвращаете результаты рекурсивного вызова.

Вам необходимо yield return каждый элемент, возвращаемый после звонка:

foreach(var x in GetAllNames(node.ChildNodes))
    yield return x;
0 голосов
/ 26 сентября 2011

Это очень интересная проблема, которая может привести к МНОГО использования ресурсов для произвольно глубоких структур.Я думаю, что мистер Скит предложил метод «сплющивания», но я не помню ссылку.Вот код, который мы используем на основе его идеи (это метод расширения в 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);
            }
        }
    }
}

Это позволяет избежать рекурсии и множества вложенных итераторов.Затем вы бы назвали это:

// try and get all names 
var names = tree.PreOrder<Node>(n => n.ChildNodes); 

Теперь это «предварительный заказ», где имя узла стоит на первом месте, но вы можете легко написать пост-заказ, если это то, что вы предпочитаете.

...