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

标签 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 的回答以提供一个我已添加到我的公共(public)库中的通用扩展方法。我可以看到这对其他人也有帮助,所以这里是:

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

谢谢克里斯!

另外,

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

最佳答案

通常有充分的理由避免(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);
}

关于c# - 如何使用 LINQ 选择复合对象的所有后代,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5262201/

相关文章:

c# - LinQ 选择一个以列表作为参数的对象

c# - AspNetUsers 的主键不一致 "Object reference not set to an instance of an object"

c# - 如何在 C# 中使用 SevenZipSharp/Unrar 知道多 rar 文件中哪个 rar 是第一个?

.net - 慢选择单节点

c# - 请求失败,HTTP 状态为 401 : Unauthorized - IIS Upgrade - WebService & Web Application

c# - ASP.NET Web API 中具有多个 GET 方法的单个 Controller

linq - EF LINQ 选择 Id 位于列表中的实体

c# - 将一个 dll 作为嵌入式资源嵌入到另一个 dll 中,然后从我的代码中调用它

C#进度条改变颜色

c# - 获取本地时区信息