26

LINQ プロバイダーを階層データ ソースに記述しています。API をどのように使用したいかを示す例を作成し、それらのユース ケースをサポートするようにコーディングすることで、API を設計するのが最も簡単だと思います。

私が問題を抱えていることの1つは、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

クエリがライト トラックの下にあるすべてのアイテムを取得する、またはすぐ近くのアイテムのみを取得するという意図を表現する最も直感的な方法は何ですか? 2 つの意図を区別するための、最も侵入的でなく、摩擦の少ない方法は?

私の一番の目標は、この LINQ プロバイダーを、LINQ を平均的に理解している他の開発者に引き渡し、再帰ラムダの作成に関するチュートリアルを提供することなく、再帰クエリとリスト クエリの両方を作成できるようにすることです。良さそうな使い方があれば、それに対してプロバイダーをコーディングできます。

追加の説明: (私はこれを伝えるのが本当に苦手です!) - この LINQ プロバイダーは外部システムに対するものであり、単にオブジェクト グラフをウォークするだけではありません。また、この特定のケースでは、再帰が実際にあらゆる種類の真の再帰アクティビティに変換されることもありません。フードの下。「深い」クエリと「浅い」クエリを区別する方法が必要なだけです。

では、どう表現するのがベストだと思いますか?それとも、私が見逃した標準的な表現方法はありますか?

4

9 に答える 9

19

最初に注意すべきことは、実際には、ラムダ式は再帰的になる可能性があるということです。いいえ、正直に!それを行うのは簡単ではなく、確かに読むのも簡単ではありません - ほとんどの LINQ プロバイダー (はるかに単純な LINQ-to-Objects を除く) は、見ているだけで咳き込みます... しかし、それは可能です。詳細については、こちらを参照してください (警告 - 頭が痛くなる可能性があります)。

でも!!それはおそらくあまり役​​に立たないでしょう...実用的なアプローチのために、私はそれを行う方法を見ていきます... orXElementを使用して再帰の一部を削除できることに注意してください:Queue<T>Stack<T>

using System;
using System.Collections.Generic;

static class Program {
    static void Main() {
        Node a = new Node("a"), b = new Node("b") { Children = {a}},
            c = new Node("c") { Children = {b}};
        foreach (Node node in c.Descendents()) {
            Console.WriteLine(node.Name);
        }
    }
}

class Node { // very simplified; no sanity checking etc
    public string Name { get; private set; }
    public List<Node> Children { get; private set; }
    public Node(string name) {
        Name = name;
        Children = new List<Node>();
    }
}
static class NodeExtensions {
    public static IEnumerable<Node> Descendents(this Node node) {
        if (node == null) throw new ArgumentNullException("node");
        if(node.Children.Count > 0) {
            foreach (Node child in node.Children) {
                yield return child;
                foreach (Node desc in Descendents(child)) {
                    yield return desc;
                }
            }
        }
    }
}

別の方法は、次のように書くことです(単一レベルSelectDeepを模倣するため):SelectMany

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

繰り返しますが、再帰を避けるためにこれを最適化していませんが、十分に簡単に実行できます。

于 2009-04-27T13:39:56.400 に答える
6

クエリの深さを制御できるように実装することもできます。

Descendants() のようなものはすべてのレベルで子孫を取得し、Descendants(0) は直接の子を取得し、Descendants(1) は子と孫を取得します...

于 2009-04-08T23:49:53.060 に答える
3

タイプに FlattenRecusively のような (拡張) メソッドを実装することをお勧めします。

from item in list.FlattenRecusively() where ... select item
于 2009-04-29T16:00:07.420 に答える
2

私はフランクに同意する必要があります。LINQ-to-XMLがこれらのシナリオをどのように処理するかを見てください。

実際、私はLINQ-to-XML実装を完全にエミュレートしますが、任意のデータ型に合わせて変更します。なぜ車輪の再発明をするのですか?

于 2009-04-09T00:13:23.987 に答える
1

あなたのオブジェクトで重い物を持ち上げても大丈夫ですか? (そこまで重くないです)

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
    }
}
于 2009-04-30T01:19:20.943 に答える