CヒドイIQueryableは、式樹の神秘的なベールをはがします。


木は何ですか?
木は何ですか?この問題はちょっと白痴のようです。木は木じゃないですか?
私たちは一番下の主幹から上を見ます。主枝-分岐-分岐…無限の分岐と言えます。私たちは逆を見てみます。

普段一番多く使われているツリー構造のデータはXMLです。ノードの下には無限にサブノードを追加できます。私達は普段またどんな木の構造のデータを使ったことがあるかを考えてみます。
今日話したのと関係がありますね。今日は主に表現ツリーを分析します。
lamda表現と表現ツリーの違い:
Lamda表現:

Func<Student, bool> func = t => t.Name == "    ";
式ツリー:

Expression<Func<Student, bool>> expression = t => t.Name == "    "; 
どう見ても、違いはないですね。式はExpressionで包んだだけです。それは間違っています。これはMicrosoftが見せてくれたカムフラージュです。コンパイルされたC((zhi)コードを見ます。

最初のlamda表現は匿名関数にコンパイルされ、第二の表現ツリーは私たちの知らないものの山にコンパイルされています。私たちが以前書いたlamdaよりずっと複雑です。
結論:
私たちが普段使っている表式ツリーは、lamda表式を作成してコンパイルした表式ツリーです。つまり、一般的に使われている表式ツリーはコンパイラで作ってくれます。もちろん、我々は手動でアクティブに表現ツリーを作成することができます。ただ面倒臭いだけで、必要な状況ではないです。誰がこの苦しい仕事をやりたいですか?)
式樹には何か不思議なところがありますか?

ちょっと見た感じがしますか?BodyにはRight、Leftがあります。RightにはRight、Leftがあります。それらのタイプはすべてExpressionから継承されています。このようなノードの下にノードがあり、無限に添付できるデータ構造をツリー構造データと呼びます。つまり私たちの表現ツリーです。
補足:上のStudentエンティティ類:

public class Student
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string Address { get; set; }

    public string Sex { get; set; }
}
解析式ツリー
上にはいわゆる表式の木を見ましたが、他にも思ったほど複雑ではありません。木の構造データじゃないですか?私たちが自分のormを実現するには、式樹を解析する必要があります。解析ツリー構造データといえば、再帰的アルゴリズムが使われます。解析式ツリーを開始します。
解析方法を先に定義します。

//     
public static class AnalysisExpression
{
    public static void VisitExpression(Expression expression)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.Call://    
                MethodCallExpression method = expression as MethodCallExpression;
                Console.WriteLine("   :" + method.Method.Name);
                for (int i = 0; i < method.Arguments.Count; i++)
                    VisitExpression(method.Arguments[i]);
                break;
            case ExpressionType.Lambda://lambda   
                LambdaExpression lambda = expression as LambdaExpression;
                VisitExpression(lambda.Body);
                break;
            case ExpressionType.Equal://    
            case ExpressionType.AndAlso://and    
                BinaryExpression binary = expression as BinaryExpression;
                Console.WriteLine("   :" + expression.NodeType.ToString());
                VisitExpression(binary.Left);
                VisitExpression(binary.Right);
                break;
            case ExpressionType.Constant://   
                ConstantExpression constant = expression as ConstantExpression;
                Console.WriteLine("   :" + constant.Value.ToString());
                break;
            case ExpressionType.MemberAccess:
                MemberExpression Member = expression as MemberExpression;
                Console.WriteLine("    :{0},  :{1}", Member.Member.Name, Member.Type.ToString());
                break;
            default:
                Console.Write("UnKnow");
                break;
        }
    }

}
コール解析方法:

Expression<Func<Student, bool>> expression = t => t.Name == "    " && t.Sex == " ";
AnalysisExpression.VisitExpression(expression);
すべてのノードを遍歴するまで、1つの階層のサブノードに再帰する。最後の印刷効果は以下の通りです。

基本的に私たちが欲しい元素と値は全部取りました。どうやって組み立てるかはあなたの気持ち次第です。sqlにするか、それとも生成urlにするか、どうぞご自由に!
自分のIQueryable、IQueryProviderを実現します。
式樹を解析しただけで、自分のormを叩くことができますか?駄目です。少なくともIQueryableインターフェースに基づいてエンコードします。
次に私達はカスタムクラスのMyQueryableインターフェースIQueryable

public class MyQueryable<T> : IQueryable<T>
 {
     public IEnumerator<T> GetEnumerator()
     {
         throw new NotImplementedException();
     }
     IEnumerator IEnumerable.GetEnumerator()
     {
         throw new NotImplementedException();
     }
     public Type ElementType
     {
         get { throw new NotImplementedException(); }
     }
     public Expression Expression
     {
         get { throw new NotImplementedException(); }
     }
     public IQueryProvider Provider
     {
         get { throw new NotImplementedException(); }
     }
 }
私達はその中にインターフェース属性IQueryProviderがあるのを見ました。このインターフェースの役割は大きいですね。主な役割はオペレータを検索する時にIQueryable<T>を再作成し、最後に遍歴した時にsql遠隔取得を実行します。また、Expression属性を見ました。
IQueryable<T>とExpression(表現ツリー)の関係が分かりましたね。
IQueryable<T>の最も主要な役割は、Expression(式樹)を記憶することです。
私達もカスタムしました。IQueryProviderインターフェースの種類MyQueryProvider:

public class MyQueryProvider : IQueryProvider
{
    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        throw new NotImplementedException();
    }
    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }
    public TResult Execute<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }
    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }
}
上は全部自動的に生成された疑似コードです。具体的な実装を満たします。

public class MyQueryProvider : IQueryProvider
    {
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new MyQueryable<TElement>(expression);
        }

        public IQueryable CreateQuery(Expression expression)
        {
            throw new NotImplementedException();
        }

        public TResult Execute<TResult>(Expression expression)
        {
            return default(TResult);
        }

        public object Execute(Expression expression)
        {
            return new List<object>();
        } 
    }  
    public class MyQueryable<T> : IQueryable<T>
    {
        public MyQueryable()
        {
            _provider = new MyQueryProvider();
            _expression = Expression.Constant(this);
        }

        public MyQueryable(Expression expression)
        {
            _provider = new MyQueryProvider();
            _expression = expression;
        }
        public Type ElementType
        {
            get { return typeof(T); }
        }

        private Expression _expression;
        public Expression Expression
        {
            get { return _expression; }
        }

        private IQueryProvider _provider;
        public IQueryProvider Provider
        {
            get { return _provider; }
        }

        public IEnumerator GetEnumerator()
        {
            return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            var result = _provider.Execute<List<T>>(_expression);
            if (result == null)
                yield break;
            foreach (var item in result)
            {
                yield return item;
            }
        }
    }
実行コード:

var aa = new MyQueryable<Student>();
 var bb = aa.Where(t => t.Name == "    ");
 var cc = bb.Where(t => t.Sex == " ");
 var dd = cc.AsEnumerable();
 var ee = cc.ToList(); 
結論:
  • 毎回Whereクエリを実行するとIQueryProviderが新しいIQueryable
  • を作成してくれます。
  • AsEnumerableメソッドを呼び出したときは、実際に値を取ることはありません。
  • ToList()メソッドを実行した時、本当にローズデージェネレータGetEnumerator()を呼び出します。
  • 本当に値を取る時は、IQueryProviderのExecuteメソッドを実行します。この方法を呼び出したときに式の数を解析し、取得結果を実行することです。
    私達は本当に事実を行うべきExecuteを見ましたが、彼をデフォルトに戻しました。

    今は嫌な人がいるかもしれません。具体的にExecuteを実現してください。はい上記の解析式樹を通して、ここでやりたいことを自分でやってもいいです。)
    まず簡単のために、集合をデータソースにします。
    
    //  Student  
    public static List<Student> StudentArrary = new List<Student>()
    {
            new Student(){Name="    ", Age=26, Sex=" ", Address="  "},
            new Student(){Name="  ", Age=23, Sex=" ", Address="  "},
            new Student(){Name=" -  ", Age=25, Sex=" ", Address="  "}
    };
    次に、Visit Expressition 2を書き換える方法:(以前との違い:現在の目的は、Sqlまたは他のものに再組み立てるのではなく、式樹における表現を取ることです。)
    
    public static void VisitExpression2(Expression expression, ref List<LambdaExpression> lambdaOut)
    {
        if (lambdaOut == null)
            lambdaOut = new List<LambdaExpression>();
        switch (expression.NodeType)
        {
            case ExpressionType.Call://    
                MethodCallExpression method = expression as MethodCallExpression;
                Console.WriteLine("   :" + method.Method.Name);
                for (int i = 0; i < method.Arguments.Count; i++)
                    VisitExpression2(method.Arguments[i], ref  lambdaOut);
                break;
            case ExpressionType.Lambda://lambda   
                LambdaExpression lambda = expression as LambdaExpression;
                lambdaOut.Add(lambda);
                VisitExpression2(lambda.Body, ref  lambdaOut);
                break;
            case ExpressionType.Equal://    
            case ExpressionType.AndAlso://and    
                BinaryExpression binary = expression as BinaryExpression;
                Console.WriteLine("   :" + expression.NodeType.ToString());
                VisitExpression2(binary.Left, ref  lambdaOut);
                VisitExpression2(binary.Right, ref  lambdaOut);
                break;
            case ExpressionType.Constant://   
                ConstantExpression constant = expression as ConstantExpression;
                Console.WriteLine("   :" + constant.Value.ToString());
                break;
            case ExpressionType.MemberAccess:
                MemberExpression Member = expression as MemberExpression;
                Console.WriteLine("    :{0},  :{1}", Member.Member.Name, Member.Type.ToString());
                break;
            case ExpressionType.Quote:
                UnaryExpression Unary = expression as UnaryExpression;
                VisitExpression2(Unary.Operand, ref  lambdaOut);
                break;
            default:
                Console.Write("UnKnow");
                break;
        }
    }
    方法Executeを再実現します。
    
    public TResult Execute<TResult>(Expression expression)
    {
        List<LambdaExpression> lambda = null;
        AnalysisExpression.VisitExpression2(expression, ref lambda);//             
        IEnumerable<Student> enumerable = null;
        for (int i = 0; i < lambda.Count; i++)
        {
            // LambdaExpression  Expression<Func<Student, bool>>  
            //    Compile()      
            Func<Student, bool> func = (lambda[i] as Expression<Func<Student, bool>>).Compile(); 
            if (enumerable == null)
                enumerable = Program.StudentArrary.Where(func);//  IEnumerable
            else
                enumerable = enumerable.Where(func);
        }
        dynamic obj = enumerable.ToList();//(  :           ,       sql       ,    url        。)
        return (TResult)obj;
    }
    実行プロセス:

    個人のIQueryable遅延負荷に対する理解:
  • 前の部分のクエリオペレータは、論理分解を式樹に格納するだけで、リモートでsqlを実行していません。
  • foreacheはIEnumerable<T>を実行するが、IEnumerable<T>は同様に遅延負荷の特性を有する。反復する度にデータを取ります。また、ナビゲーション属性を使用すると、再度データベースを検索します。今度は遅延ロードと言ってIEnumerableの功労を忘れないでください。)
  • 豆知識:
    式ツリーをLamda式に変換します。
    
    Expression<Func<Student, bool>> expression = t => t.Name == "    ";
    Func<Student, bool> func = expression.Compile();
    締め括りをつける
    表式ツリーの分析が一段落しました。まだ多くの詳細があります。または重要なことは分析されていません。次回は新しい心得があります。
    感覚表式ツリーとは、まず表式をツリー構造に散在させて(一般的には散逸の過程でコンパイラが完了します)、その後、異なるデータソースやインターフェースによって自分の欲しい形式に再編成することができます。これも自分のormを可能にします。
    今日は主に表現ツリーの解析と、自分を実現するIQueryableとIQueryProviderに対して記録とまとめを行いました。中には間違った結論や言い方があるかもしれません。軽く撮ります。
    以上はC萼IQueryable<T>式樹の神秘的なベールの詳細を明らかにしました。C〓IQueryable<T>に関する資料は他の関連記事に注目してください。