C# 用LINQ表示递归

C# 用LINQ表示递归,c#,.net,linq,recursion,hierarchical-data,C#,.net,Linq,Recursion,Hierarchical Data,我正在为层次数据源编写LINQ提供程序。我发现设计API最简单的方法是编写示例来说明我希望如何使用它,然后编写代码来支持这些用例 我遇到的一个问题是在LINQ语句中用一种简单/可重用/优雅的方式来表示“深度查询”或递归。换句话说,区分以下各项的最佳方式是什么: from item in immediate-descendants-of-current-node where ... select item 与: from item in all-descendants-of-current-no

我正在为层次数据源编写LINQ提供程序。我发现设计API最简单的方法是编写示例来说明我希望如何使用它,然后编写代码来支持这些用例

我遇到的一个问题是在LINQ语句中用一种简单/可重用/优雅的方式来表示“深度查询”或递归。换句话说,区分以下各项的最佳方式是什么:

from item in immediate-descendants-of-current-node where ... select item
与:

from item in all-descendants-of-current-node where ... select item
(编辑:请注意上面的例子都不一定反映我想要的查询结构。我对任何表示递归/深度的好方法感兴趣)

请注意我不是在问如何实现这样的提供程序,或者如何以允许递归的方式编写IQueryable或IEnumerable。我从一个编写LINQ查询并使用我的提供者的角度提出问题——对于他们来说,什么是一种直观的方式来表示他们是否希望递归

数据结构类似于典型的文件系统:文件夹可以包含子文件夹的集合,文件夹也可以包含项目的集合。所以myFolder.Folders表示myFolder和myFolder.Items的直接子文件夹,其中包含myFolder中的所有项目。下面是一个站点层次结构的基本示例,非常类似于包含文件夹和页面的文件系统:

(F)Products
    (F)Light Trucks
        (F)Z150
            (I)Pictures
            (I)Specs
            (I)Reviews
        (F)Z250
            (I)Pictures
            (I)Specs
            (I)Reviews
        (F)Z350
            (I)Pictures
            (I)Specs
            (I)Reviews
        (I)Splash Page
    (F)Heavy Trucks
    (F)Consumer Vehicles
    (I)Overview 
如果我写:

from item in lightTrucks.Items where item.Title == "Pictures" select item
表达查询获取轻型卡车下的所有项目或仅获取直接项目的意图的最直观的方式是什么?区分这两种意图的干扰最小、摩擦最小的方法

我的#1目标是能够将此LINQ提供程序移交给对LINQ有一般理解的其他开发人员,并允许他们编写递归和列表查询,而无需向他们提供编写递归lambda的教程。给定一个看起来不错的用法,我可以针对它编写提供程序代码

补充说明:(我真的很擅长沟通!)-这个LINQ提供者是一个外部系统,它不是简单地遍历一个对象图,在这种特定情况下,递归表达式实际上也不会转化为任何类型的真正的递归活动。只需要一种区分“深”查询和“浅”查询的方法


那么,你认为最好的表达方式是什么?还是有一种我没有注意到的标准表达方式?

Linq toXml处理得很好,有一个XElement.Elements()/.Nodes()操作来获取直接子代,还有一个XElement.grounds()/groundnodes()操作来获取所有子代。你认为这是一个例子吗?

要总结Linq到Xml的行为。。。导航函数各自对应于XPath()中的轴类型。如果导航功能选择元素,则使用轴名称。如果导航功能选择节点,则轴名称将与附加的节点一起使用

例如,有函数subjects()和degenantsnode()对应于XPath的后代轴,返回XElement或XNode

例外情况是最常用的情况,即子轴,这并不奇怪。在XPath中,如果未指定轴,则使用该轴。为此,linq到xml导航函数不是Children()和ChildrenNodes(),而是Elements()和Nodes()

XElement是XNode的一个子类型。XNode包括HTML标记,但也包括HTML注释、cdata或文本。Xelement是XNode的一种类型,但具体指的是HTML标记。因此,Xelement具有标记名,并支持导航功能

现在,将Linq中的导航链接到XML不像XPath那么容易。问题是导航函数返回集合对象,而导航函数应用于非集合。考虑XPath表达式,它将表标签选择为直接子代,然后选择任何后代表数据标记。我认为这看起来像“/children::table/substands::td”或“/table/substands::td”


使用IEnumerable::SelectMany()可以调用集合上的导航函数。类似于上面的.Elements(“table”).SelectMany(T=>T.substands(“td”)

我只需要实现两个函数来明确区分这两个选项(Children vs.fullDecentants)或重载GetChildren(bool returnDecentants)。每个函数都可以实现IEnumerable,所以这只是它们将哪个函数传递到其LINQ语句中的问题。

我会以这样一种方式来实现它,以控制我希望查询的深度


类似于subjects()的东西可以在所有级别检索后代,而subjects(0)可以检索直系子代,subjects(1)可以获取子代和孙辈等等…

我必须同意Frank的观点。看看LINQtoXML是如何处理这些场景的


事实上,我将完全模拟LINQ到XML的实现,但将其更改为任何数据类型。为什么要重新设计方向盘?

首先要注意的是,实际上,lambda表达式可以是递归的。不,老实说!这不容易做到,当然也不容易阅读-见鬼,大多数LINQ提供程序(除了简单得多的LINQ to对象)只要看一眼就会咳嗽。。。但是这是可能的。对于完整的血淋淋的细节(警告-脑疼痛可能)

然而!!那可能帮不了什么忙。。。对于一个实用的方法,我会看看
XElement
etc这样做的方式。。。注意:您可以使用
队列
堆栈
删除一些递归:


同样,我没有对此进行优化以避免递归,但它可以很容易地完成。

您可能希望为您的类型实现一个类似于fluntRecurly的(扩展)方法

from item in list.FlattenRecusively() where ... select item

你可以在你的物体上举重吗?(甚至没有那么重)

usi
public static class EnumerableExtensions
{
    public static IEnumerable<T> SelectDeep<T>(
        this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
    {
        foreach (T item in source)
        {
            yield return item;
            foreach (T subItem in SelectDeep(selector(item),selector))
            {
                yield return subItem;
            }
        }
    }
}
public static class NodeExtensions
{
    public static IEnumerable<Node> Descendents(this Node node)
    {
        if (node == null) throw new ArgumentNullException("node");
        return node.Children.SelectDeep(n => n.Children);
    }
}
from item in list.FlattenRecusively() where ... select item
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace LinqRecursion
{
    class Program
    {
        static void Main(string[] args)
        {
            Person mom = new Person() { Name = "Karen" };
            Person me = new Person(mom) { Name = "Matt" };
            Person youngerBrother = new Person(mom) { Name = "Robbie" };
            Person olderBrother = new Person(mom) { Name = "Kevin" };
            Person nephew1 = new Person(olderBrother) { Name = "Seth" };
            Person nephew2 = new Person(olderBrother) { Name = "Bradon" };
            Person olderSister = new Person(mom) { Name = "Michelle" };

            Console.WriteLine("\tAll");
            //        All
            //Karen 0
            //Matt 1
            //Robbie 2
            //Kevin 3
            //Seth 4
            //Bradon 5
            //Michelle 6
            foreach (var item in mom)
                Console.WriteLine(item);

            Console.WriteLine("\r\n\tOdds");
            //        Odds
            //Matt 1
            //Kevin 3
            //Bradon 5
            var odds = mom.Where(p => p.ID % 2 == 1);
            foreach (var item in odds)
                Console.WriteLine(item);

            Console.WriteLine("\r\n\tEvens");
            //        Evens
            //Karen 0
            //Robbie 2
            //Seth 4
            //Michelle 6
            var evens = mom.Where(p => p.ID % 2 == 0);
            foreach (var item in evens)
                Console.WriteLine(item);

            Console.ReadLine();

        }
    }

    public class Person : IEnumerable<Person>
    {
        private static int _idRoot;

        public Person() {
            _id = _idRoot++;
        }

        public Person(Person parent) : this()
        {
            Parent = parent;
            parent.Children.Add(this);
        }

        private int _id;
        public int ID { get { return _id; } }
        public string Name { get; set; }

        public Person Parent { get; private set; }

        private List<Person> _children;
        public List<Person> Children
        {
            get
            {
                if (_children == null)
                    _children = new List<Person>();
                return _children;
            }
        }

        public override string ToString()
        {
            return Name + " " + _id.ToString();
        }

        #region IEnumerable<Person> Members

        public IEnumerator<Person> GetEnumerator()
        {
            yield return this;
            foreach (var child in this.Children)
                foreach (var item in child)
                    yield return item;
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}
from item in AllItems where item.Parent == currentNode select item
from item in AllItems where item.Ancestors.Contains(currentNode) select item