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

发布于 2024-10-21 03:45:54 字数 1654 浏览 8 评论 0原文

如何使用 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; }
}

解答

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

    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。他的回答提供了一种更好的方法来解决这个问题,但我没有选择它,因为它不是我的问题的直接答案。

How can I make ComponentTraversal.GetDescendants() better using LINQ?

Question

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

Answer

I edited Chris's answer to provide a generic extension method that I've added to my Common library. I can see this being helpful for other people as well so here it is:

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

Thanks Chris!

Also,

Please look at LukeH's answer at http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx . His answer provides a better way to approach this problem in general, but I did not select it because it was not a direct answer to my question.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

旧瑾黎汐 2024-10-28 03:45:55

我不知道更好,但我认为这执行相同的逻辑:

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

它可能更短,但你所拥有的没有任何问题。正如我上面所说,这应该执行相同的事情,我怀疑该函数的性能是否得到了提高。

I don't know about better, but I think this performs the same logic:

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

It might be shorter, but there is nothing wrong with what you have. As I said above, this is supposed to perform the same thing and I doubt that the performance of the function is improved.

缱倦旧时光 2024-10-28 03:45:55

当您可能想要实现迭代器时,这是一个很好的示例。这具有以稍微更具可读性的语法进行惰性求值的优点。另外,如果您需要添加额外的自定义逻辑,那么此表单更具可扩展性

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

This is a good example for when you might want to implement an iterator. This has the advantage of lazy evaluation in a slightly more readable syntax. Also, if you need to add additional custom logic then this form is more extensible

 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;
        }
    }
醉态萌生 2024-10-28 03:45:54

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

There are often good reasons to avoid (1) recursive method calls, (2) nested iterators, and (3) lots of throwaway allocations. This method avoids all of those potential pitfalls:

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

And here's the generic equivalent:

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);
}
衣神在巴黎 2024-10-28 03:45:54
var result = composite.Children.OfType<Composite>().SelectMany(child => child.GetDescendants()).Concat(composite.Children);
return result.ToList();

当从命令式语法转换为 LINQ 时,一次一步地进行转换通常非常容易。这是它的工作原理:

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

When doing a translation from imperitive syntax to LINQ, it is usually pretty easy to take the translation one step at a time. Here is how this works:

  1. This is looping over composite.Children, so that will be the collection we apply LINQ to.
  2. There are two general operations occuring in the loop, so lets do one of them at a time
  3. The "if" statement is performing a filter. Normally, we would use "Where" to perform a filter, but in this case the filter is based on type. LINQ has "OfType" built in for this.
  4. For each child composite, we want to recursively call GetDescendants and add the results to a single list. Whenever we want to transform an element into something else, we use either Select or SelectMany. Since we want to transform each element into a list and merge them all together, we use SelectMany.
  5. Finally, to add in the composite.Children themselves, we concatenate those results to the end.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文