web-dev-qa-db-ja.com

ExpressionVisitorを使用する理由は何ですか?

方法:式ツリーの変更 に関するMSDNの記事から、ExpressionVisitorが何をすべきかを知っています。式を変更する必要があります。

しかし、彼らの例はかなり現実的ではないので、なぜそれが必要なのかと思っていました。式ツリーを変更することが理にかなっている実際のケースをいくつか挙げてください。または、なぜそれをまったく変更する必要があるのですか?何から何へ?

また、あらゆる種類の式を訪問するための多くのオーバーロードがあります。それらのどれをいつ使用し、何を返す必要があるかを知るにはどうすればよいですか? VisitParameterを使用してbase.VisitParameter(node)を返す人がいる一方で、もう一方はExpression.Parameter(..)を返していました。

24
t3chb0t

式ツリーを変更することが理にかなっている実際のケースをいくつか挙げてください。

厳密に言えば、式ツリーは不変であるため、変更することはありません(外部から見ると、少なくとも、内部で値をメモしたり、変更可能なプライベート状態を保持しているという保証はありません)。それは不変であるためであり、ノードを変更することはできないため、ビジターパターンが、特定の方法(異なる方法(不変オブジェクトの変更に最も近いものです)。

Linq自体にいくつかあります。

多くの点で最も単純なLinqプロバイダーは、メモリ内の列挙可能なオブジェクトを処理するlinq-to-objectsプロバイダーです。

列挙型を_IEnumerable<T>_オブジェクトとして直接受け取る場合、ほとんどのプログラマーはほとんどのメソッドの最適化されていないバージョンを非常に迅速に作成できるため、非常に単純です。例えば。 Whereは次のとおりです。

_foreach (T item in source)
  if (pred(item))
    yield return item;
_

等々。しかし、EnumerableQueryableが_IQueryable<T>_バージョンを実装するのはどうですか? EnumerableQueryableは_IEnumerable<T>_をラップするので、関係する1つ以上の列挙可能なオブジェクトに対して目的の操作を実行できますが、その操作を_IQueryable<T>_およびその他の式で説明する式がありますセレクター、述語など。必要なのは、その演算の_IEnumerable<T>_に関する説明と、セレクター、述語などのデリゲートです。

_System.Linq.EnumerableRewriter_ExpressionVisitorの実装であり、まさにそのような再書き込みを行うため、結果を単純にコンパイルして実行できます。

_System.Linq.Expressions_自体には、さまざまな目的でExpressionVisitorの実装がいくつかあります。 1つの例は、インタープリター形式のコンパイルは引用符で囲まれた式のホイストされた変数を直接処理できないため、訪問者を使用してそれを辞書のインデックスでの作業に書き換えます。

ExpressionVisitorは、別の式を生成するだけでなく、別の結果を生成することもできます。ここでも_System.Linq.Expressions_自体に内部の例があり、問題の式にアクセスすることで機能する多くの式タイプのデバッグ文字列とToString()があります。

これは(そうである必要はありませんが)データベースクエリlinqプロバイダーが式をSQLクエリに変換するために使用するアプローチである可能性があります。

それらのどれをいつ使用し、何を返す必要があるかを知るにはどうすればよいですか?

これらのメソッドのデフォルトの実装は次のとおりです。

  1. 式に子式を含めることができない場合(Expression.Constant()の結果など)、ノードを再び返します。
  2. それ以外の場合は、すべての子式にアクセスし、問題の式でUpdateを呼び出して、結果を返します。 Updateは、新しい子を持つ同じタイプの新しいノードを返すか、子が変更されていない場合は同じノードを返します。

したがって、目的が何であれ、ノードを明示的に操作する必要があることがわからない場合は、おそらくそれを変更する必要はありません。また、Updateは、部分的な変更のためにノードの新しいバージョンを取得する便利な方法です。ただし、「目的が何であるか」が何を意味するかは、もちろんユースケースによって異なります。最も一般的なケースはおそらくどちらか一方の極端に行き、1つまたは2つの式タイプがオーバーライドを必要とするか、すべてまたはほとんどすべてがオーバーライドを必要とします。

(1つの注意点は、ステップと変数の両方のReadOnlyCollectionまたはキャッチブロックのBlockExpressionなどのTryExpressionに子があるノードの子を調べる場合です。 、場合によってはそれらの子を変更するだけですが、変更していない場合は、自分でこれを欠陥として確認することをお勧めします[最近修正されましたが、まだリリースされたバージョンではありません]は、同じ子をUpdateは、元のReadOnlyCollectionとは異なるコレクションにあり、不要な新しい式が作成され、ツリーの上位に影響を及ぼします。これは通常は無害ですが、時間とメモリを浪費します)。

8
Jon Hanna

データベース上に0または1(数値)を含むフィールドがあり、アプリケーションでブールを使用したいという問題がありました。

解決策は、0または1を含み、ブール値に変換された「フラグ」オブジェクトを作成することでした。すべてのアプリケーションでboolのように使用しましたが、.Where()句で使用した場合、EntityFrameworkは変換メソッドを呼び出せないことを報告しました。

したがって、式ビジターを使用して、.Where(x => x.Property)などのすべてのプロパティアクセスを.Where(x => x.Property.Value == 1)に変更してから、ツリーをEFに送信します。

10

ExpressionVisitor は、Expressionビジターパターン を有効にします。

概念的には、問題は、Expressionツリーをナビゲートするときに、特定のノードがExpressionであることを知っているだけですが、Expressionの種類が明確にわからないということです。このパターンにより、使用しているExpressionの種類を確認し、種類ごとにタイプ固有の処理を指定できます。

Expressionがある場合は、_.Modify_を呼び出すだけです。 Expressionは独自の型を知っているため、適切なoverrideを呼び出します。

リンクしたMSDNの例 を見てください。

_public class AndAlsoModifier : ExpressionVisitor  
{  
    public Expression Modify(Expression expression)  
    {  
        return Visit(expression);  
    }  

    protected override Expression VisitBinary(BinaryExpression b)  
    {  
        if (b.NodeType == ExpressionType.AndAlso)  
        {  
            Expression left = this.Visit(b.Left);  
            Expression right = this.Visit(b.Right);  

            // Make this binary expression an OrElse operation instead of an AndAlso operation.  
            return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method);  
        }  

        return base.VisitBinary(b);  
    }  
}
_

この例では、ExpressionBinaryExpressionである場合、例で指定されているVisitBinary(BinaryExpression b)をコールバックします。これで、BinaryExpressionであることを認識して、BinaryExpressionを処理できます。他の種類のoverrideを処理する他のExpressionメソッドを指定することもできます。

これはオーバーロードされた解決策であるため、Expressionにアクセスすると、最適なメソッドが呼び出されることに注意してください。したがって、異なる種類のBinaryExpressionがある場合は、特定の1つのサブタイプに対してoverrideを記述できます。別のサブタイプがコールバックする場合は、デフォルトのBinaryExpression処理を使用します。

簡単に言うと、このパターンを使用すると、Expressionツリーを移動して、使用しているExpressionの種類を知ることができます。

5
Nat

EF Coreに移行し、Sql Server(MS固有)からSqlLite(プラットフォームに依存しない)に移行するときに発生した具体的な実世界の例。

既存のビジネスロジックは、フルテキスト検索(FTS)がSQL Serverで行われるバックグラウンドで自動的に発生することを前提とした中間層/サービスレイヤーインターフェイスを中心に展開されていました。検索関連のクエリは、SQL Serverストアに対する式とFTSを介してこの層に渡され、追加のFTS固有のエンティティは必要ありませんでした。

私はこれを変更したくありませんでしたが、SqlLiteを使用して全文検索の特定の仮想テーブルをターゲットにする必要があります。つまり、すべての中間層の呼び出しを変更して、FTSテーブル/エンティティを再ターゲットしてから結合する必要がありました。それらをビジネスエンティティテーブルに追加して、同様の結果セットを取得します。

しかし、ExpressionVisitorをサブクラス化することで、DALレイヤーの呼び出しをインターセプトし、着信式(またはより正確には検索式全体の一部のBinaryExpressions)を書き換えて、SqlLites FTS要件を具体的に処理することができました。

これは、データストアのデータレイヤーの特殊化が、リポジトリの基本クラス内の単一の場所から呼び出された単一のクラス内で発生したことを意味しました。 EFCoreを介してFTSをサポートするためにアプリケーションの他の側面を変更する必要はなく、SqlLite FTS関連のエンティティを単一のプラグイン可能なアセンブリに含めることができます。

したがって、ExpressionVisitorは非常に便利です。特に、さまざまな形式のIPCを介してデータとして式ツリーを渡すことができるという概念全体と組み合わせると、非常に便利です。

2
rism