如何使用LINQ选择复合对象的所有后代

时间:2011-03-10 15:54:37

标签: c# linq .net-3.5 linq-to-objects composite

如何使用LINQ更好地ComponentTraversal.GetDescendants()

问题

public static class ComponentTraversal
{
    public static IEnumerable<Component> GetDescendants(this Composite composite)
    {
        //How can I do this better using LINQ?
        IList<Component> descendants = new Component[]{};
        foreach(var child in composite.Children)
        {
            descendants.Add(child);
            if(child is Composite)
            {
                descendants.AddRange((child as Composite).GetDescendants());
            }
        }
        return descendants;
    }
}
public class Component
{
    public string Name { get; set; }
}
public class Composite: Component
{
    public IEnumerable<Component> Children { get; set; }
}
public class Leaf: Component
{
    public object Value { get; set; }
}

答案

我编辑了Chris的答案,提供了我已添加到Common库中的通用扩展方法。我可以看到这对其他人也有帮助,所以这里是:

    public static IEnumerable<T> GetDescendants<T>(this T component, Func<T,bool> isComposite, Func<T,IEnumerable<T>> getCompositeChildren)
    {
        var children = getCompositeChildren(component);
        return children
            .Where(isComposite)
            .SelectMany(x => x.GetDescendants(isComposite, getCompositeChildren))
            .Concat(children);
    }

谢谢克里斯!

此外,

请在http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx查看LukeH的答案。他的回答提供了更好的方法来解决这个问题,但我没有选择它,因为它不是我问题的直接答案。

4 个答案:

答案 0 :(得分:7)

通常有充分的理由避免(1)递归方法调用,(2)嵌套迭代器和(3)很多一次性分配。这种方法避免了所有这些潜在的陷阱:

public static IEnumerable<Component> GetDescendants(this Composite composite)
{
    var stack = new Stack<Component>();
    do
    {
        if (composite != null)
        {
            // this will currently yield the children in reverse order
            // use "composite.Children.Reverse()" to maintain original order
            foreach (var child in composite.Children)
            {
                stack.Push(child);
            }
        }

        if (stack.Count == 0)
            break;

        Component component = stack.Pop();
        yield return component;

        composite = component as Composite;
    } while (true);
}

这是通用的等价物:

public static IEnumerable<T> GetDescendants<T>(this T component,
    Func<T, bool> hasChildren, Func<T, IEnumerable<T>> getChildren)
{
    var stack = new Stack<T>();
    do
    {
        if (hasChildren(component))
        {
            // this will currently yield the children in reverse order
            // use "composite.Children.Reverse()" to maintain original order
            // or let the "getChildren" delegate handle the ordering
            foreach (var child in getChildren(component))
            {
                stack.Push(child);
            }
        }

        if (stack.Count == 0)
            break;

        component = stack.Pop();
        yield return component;
    } while (true);
}

答案 1 :(得分:3)

我不知道更好,但我认为这表现出相同的逻辑:

public static IEnumerable<Component> GetDescendants(this Composite composite)
{
    return composite.Children
                .Concat(composite.Children
                            .Where(x => x is Composite)
                            .SelectMany(x => x.GetDescendants())
                );
}

可能会更短,但你所拥有的东西并没有错。正如我上面所说,这应该执行相同的事情,我怀疑功能的性能是否得到改善。

答案 2 :(得分:3)

var result = composite.Children.OfType<Composite>().SelectMany(child => child.GetDescendants()).Concat(composite.Children);
return result.ToList();

在执行从不完美语法到LINQ的转换时,通常很容易一次一步地进行转换。这是如何工作的:

  1. 这是循环复合.Children,所以这将是我们应用LINQ的集合。
  2. 循环中有两个常规操作,所以让我们一次执行其中一个操作
  3. “if”语句正在执行过滤器。通常,我们会使用“Where”来执行过滤器,但在这种情况下,过滤器基于类型。 LINQ内置了“OfType”。
  4. 对于每个子复合,我们希望递归调用GetDescendants并将结果添加到单个列表中。每当我们想要将元素转换为其他元素时,我们都使用Select或SelectMany。由于我们希望将每个元素转换为列表并将它们合并在一起,因此我们使用SelectMany。
  5. 最后,要在composite.Children中添加,我们将这些结果连接到最后。

答案 3 :(得分:1)

这是您可能想要实现迭代器的一个很好的示例。这具有以稍微更易读的语法进行延迟评估的优点。此外,如果您需要添加其他自定义逻辑,则此表单更具可扩展性

 public static IEnumerable<Component> GetDescendants(this Composite composite)
    {
        foreach(var child in composite.Children)
        {
            yield return child;
            if(!(child is Composite))
               continue;

            foreach (var subChild in ((Composite)child).GetDescendants())
               yield return subChild;
        }
    }