7

私はこの回答を読み、それが強調する特定のケースを理解しました。これは、別のラムダ内にラムダがあり、誤って内側のラムダも外側のものとコンパイルしたくない場合です。外側のラムダ式をコンパイルするとき、内側のラムダ式を式ツリーのままにしておく必要があります。そうです、内側のラムダ式を引用するのは理にかなっています。

しかし、それはそれについてです、私は信じています。ラムダ式を引用する他のユースケースはありますか?

そうでない場合、すべての LINQ 演算子、つまり、クラスIQueryable<T>で宣言されている拡張機能がQueryable、その情報を にパッケージ化するときに引数として受け取る述語またはラムダを引用するのはなぜですかMethodCallExpression

私は例 (および過去数日間の他のいくつか) を試しましたが、この場合、ラムダを引用する意味がないようです。

唯一のパラメーターとして (デリゲート インスタンスではなく) ラムダ式を期待するメソッドへのメソッド呼び出し式を次に示します。

MethodCallExpression次に、ラムダ内にラップしてコンパイルします。

LambdaExpressionしかし、それは内部(メソッドへの引数)もコンパイルしませんGimmeExpression。内部ラムダ式を式ツリーとして残し、そのデリゲート インスタンスを作成しません。

実際、引用せずにうまく機能します。

そして、引数を引用すると、それが壊れて、間違ったタイプの引数をメソッドに渡していることを示すエラーが表示されますGimmeExpression

どうしたんだ?この引用は一体何ですか?

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = Expression.Call(null, methodInfo, lambdaExpression);

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}
4

1 に答える 1

5

引数を次のように渡す必要がありますConstantExpression

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = 
      Expression.Call(null, methodInfo, Expression.Constant(lambdaExpression));

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}

理由は明らかです。定数値を渡しているので、ConstantExpression. 式を直接渡すことで、「expこの複雑な式ツリーから値を取得する」ことを明示的に示しています。そして、その式ツリーは実際には の値を返さないExpression<Func<bool>>ため、エラーが発生します。

仕組みIQueryableはこれとはあまり関係ありません。の拡張メソッドはIQueryable、式に関するすべての情報を保持する必要があります。これには、ParameterExpressions などの型と参照が含まれます。これは、実際には何もしないためです。式ツリーを構築するだけです。を呼び出すと、実際の作業が行われますqueryable.Provider.Execute(expression)。基本的に、これは、継承 (/インターフェースの実装) ではなく合成を行っているにもかかわらず、ポリモーフィズムが保持される方法です。しかし、IQueryable拡張メソッド自体はショートカットを実行できないことを意味します。拡張メソッドIQueryProviderは、実際にクエリを解釈する方法について何も知らないため、何も捨てることができません。

ただし、これによって得られる最も重要な利点は、クエリとサブクエリを作成できることです。次のようなクエリを検討してください。

from item in dataSource
where item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2
select item;

さて、これを訳すと以下のようになります。

dataSource.Where(item => item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2);

外側のクエリは非常に明白Whereです。指定された述語で a を取得します。ただし、内部クエリは実際には to になりCallWhere実際の述語を引数として取ります。

メソッドの実際の呼び出しが実際にWhereメソッドの に変換されることを確認することで、これらのケースは両方とも同じになり、LINQProvider は少し単純になります:)CallWhere

私は実際に を実装しない LINQ プロバイダーを作成しましたがIQueryable、実際には のようなメソッドに便利なロジックが含まれていますWhere。これははるかに単純で効率的ですが、上記の欠点があります。サブクエリを処理する唯一の方法はInvokeCall式を手動で取得して「実際の」述語式を取得することです。そうですね、単純な LINQ クエリの場合、これはかなりのオーバーヘッドです。

そしてもちろん、クエリ可能なさまざまなプロバイダーを構成するのに役立ちますが、1 つのクエリで 2 つの完全に異なるプロバイダーを使用する例は実際には見たことがありません。

Expression.Constantとの違いについては、Expression.Quoteどちらかというと似ているように見えます。決定的な違いは、クロージャーではなく実際の定数Expression.Constantとしてクロージャーを扱うことです。一方、クロージャーの「クロージャーネス」を保持します。なんで?クロージャーオブジェクト自体:)として渡されるため、ツリーは [...] のラムダのラムダのラムダを実行しているため、クロージャーのセマンティクスをどの時点でも失いたくありません。Expression.QuoteExpression.ConstantIQueryable

于 2015-05-07T15:58:59.867 に答える