私はラムダとを理解しています Func
そしてAction
デリゲートます。しかし、表現は私を困らせます。
どのような状況Expression<Func<T>>
で、平凡な古いものではなく使用しFunc<T>
ますか?
私はラムダとを理解しています Func
そしてAction
デリゲートます。しかし、表現は私を困らせます。
どのような状況Expression<Func<T>>
で、平凡な古いものではなく使用しFunc<T>
ますか?
回答:
ラムダ式を式ツリーとして扱い、実行するのではなく内部を調べたい場合。たとえば、LINQ to SQLは式を取得して同等のSQLステートメントに変換し、(ラムダを実行するのではなく)サーバーに送信します。
概念的には、Expression<Func<T>>
ある全く違うからFunc<T>
。Func<T>
はdelegate
メソッドへのほとんどのポインタであり、ラムダ式のツリーデータ構造をExpression<Func<T>>
示します。このツリー構造は、ラムダ式が実際のことを行うのではなく、何を行うかを示します。これは基本的に、式、変数、メソッド呼び出しなどの構成に関するデータを保持します(たとえば、このラムダは定数+パラメータなどの情報を保持します)。この説明を使用して、それを実際のメソッドに変換(または)したり、他の処理(LINQ to SQLの例など)を使用したりできます。ラムダを匿名メソッドおよび式ツリーとして扱う行為は、純粋にコンパイル時のことです。Expression.Compile
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
何も取得せずに10を返すILメソッドに効果的にコンパイルされます。
Expression<Func<int>> myExpression = () => 10;
パラメータを取得せず、値10を返す式を記述するデータ構造に変換されます。
どちらもコンパイル時には同じように見えますが、コンパイラが生成するものはまったく異なります。
Expression
には、特定のデリゲートに関するメタ情報が含まれています。
Expression<Func<...>>
だけではなく使用する場合、デリゲートは存在しませんFunc<...>
。
(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
ような式はExpressionTreeであり、Ifステートメントのブランチが作成されます。
初心者向けの回答を追加しているのは、これらの回答がどれほど単純であるかを理解するまで、頭に浮かんだためです。時々、複雑であるために「頭を包む」ことができないのはあなたの期待です。
LINQ-to-SQLを一般的に使用しようとする本当に煩わしい「バグ」に踏み込むまで、違いを理解する必要はありませんでした。
public IEnumerable<T> Get(Func<T, bool> conditionLambda){
using(var db = new DbContext()){
return db.Set<T>.Where(conditionLambda);
}
}
より大きなデータセットでOutofMemoryExceptionsを取得し始めるまで、これはうまくいきました。ラムダ内にブレークポイントを設定すると、ラムダ条件に一致するものを探すためにテーブルの各行を1つずつ繰り返し処理していることに気付きました。LINQ-to-SQLを想定されているように実行するのではなく、データテーブルを巨大なIEnumerableとして扱うのはなぜですか?また、LINQ-to-MongoDbの対応物でもまったく同じことを行っていました。
修正は単にに変わるFunc<T, bool>
ことでしたExpression<Func<T, bool>>
ので、なぜのExpression
代わりにが必要なのかをググって、Func
ここで終わりました。
式は単にデリゲートをそれ自体に関するデータに変換します。したがってa => a + 1
、「左側にint a
。があります。右側に1を追加します」のようなものになります。それでおしまい。もう帰れます。それは明らかにそれよりも構造化されていますが、それは本質的にすべての式ツリーが本当にすべてであり、頭を包むものは何もありません。
それを理解すると、LINQ-to-SQLがを必要Expression
とし、a Func
が適切でない理由が明らかになります。Func
SQL / MongoDb /その他のクエリに変換する方法の要点を確認するために、それ自体を取得する方法は含まれていません。それが加算、乗算、減算のどちらを実行しているかはわかりません。あなたができることはそれを実行することだけです。Expression
一方、では、デリゲートの内部を見て、やりたいことをすべて見ることができます。これにより、デリゲートをSQLクエリのように好きなように変換できます。Func
DbContextがラムダ式の内容を知らないため、機能しませんでした。このため、ラムダ式をSQLに変換できませんでした。しかし、それは次善の策であり、テーブルの各行を条件として繰り返しました。
編集:ジョン・ピーターの要求で私の最後の文章を説明する:
IQueryableはIEnumerableを拡張するため、IEnumerableのメソッドはWhere()
、を受け入れるオーバーロードを取得しますExpression
。にを渡すExpression
と、結果としてIQueryableが保持されますが、を渡すFunc
と、ベースIEnumerableにフォールバックし、結果としてIEnumerableを取得します。言い換えると、気づかずに、データセットをクエリするのではなく、反復するリストに変換しました。実際に署名を確認するまで、違いに気づくことは困難です。
Expression vs Funcを選択する際の非常に重要な考慮事項は、LINQ to EntitiesのようなIQueryableプロバイダーは、式で渡すものを「ダイジェスト」できますが、Funcで渡すものは無視するということです。この件に関して2つのブログ投稿があります。
エンティティフレームワークを使用 した式とFuncの詳細とLINQを使用して恋に落ちる-パート7:式と関数(最後のセクション)
私は、違いについていくつかのメモを追加したいFunc<T>
とExpression<Func<T>>
:
Func<T>
普通の昔ながらのMulticastDelegateです。Expression<Func<T>>
式ツリーの形式でのラムダ式の表現です。Func<T>
。ExpressionVisitor
。Func<T>
ます。Expression<Func<T>>
ます。コードサンプルで詳細を説明する記事があります:
LINQ:Func <T>対Expression <Func <T >>。
お役に立てれば幸いです。
Krzysztof Cwalinaの本(Framework Design Guidelines:Conventions、Idioms、and Patterns for Reusable .NET Libraries)から、より哲学的な説明があります。
非イメージバージョンの編集:
ほとんどの場合、コードを実行するだけでFuncまたはActionが必要になります。実行前にコードを分析、シリアル化、または最適化する必要がある場合は、式が必要です。式はコード、Func / Actionを考えるためのものですはを実行するためのものです。
database.data.Where(i => i.Id > 0)
として実行する必要がありますSELECT FROM [data] WHERE [id] > 0
。Funcを渡すだけの場合、ドライバーにブラインドを配置し、ドライバーが実行できるすべてのことを実行します。SELECT *
その後、そのデータがすべてメモリに読み込まれると、それぞれを反復処理し、id> 0のすべてをフィルターで除外しますFunc
。Expression
権限を与えますを分析してFunc
Sql / MongoDb /その他のクエリに変換するドライバー。
Expression
が、休暇中はFunc/Action
;)
LINQは標準的な例(たとえば、データベースとの対話)ですが、実際には、実際に行うのではなく、何をすべきかを表現することに常に関心があります。たとえば、protobuf-netの RPCスタックで(コード生成などを回避するために)このアプローチを使用しているため、次のようにメソッドを呼び出します。
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
これにより、解決する式ツリーSomeMethod
(および各引数の値)が分解され、RPC呼び出しが実行され、ref
/ out
argsが更新され、リモート呼び出しの結果が返されます。これは、式ツリーを介してのみ可能です。これについては、ここで詳しく説明します。
もう1つの例は、ジェネリックオペレーターコードによって行われるように、ラムダにコンパイルする目的で式ツリーを手動で構築する場合です。
主な理由は、コードを直接実行するのではなく、調べたい場合です。これにはさまざまな理由が考えられます。
Expression
任意の式に任意のデリゲート/メソッド参照の呼び出しを含めることができるため、デリゲートと同じようにシリアライズすることは不可能です。もちろん「簡単」は相対的です。
パフォーマンスについての回答はまだありません。パッシングFunc<>
に秒Where()
かCount()
悪いです。本当に悪い。あなたは使用している場合はFunc<>
、それが呼び出すIEnumerable
代わりにLINQのものIQueryable
テーブル全体が中に引っ張られ得ることを意味し、その後、フィルタを。 Expression<Func<>>
特に、別のサーバーにあるデータベースにクエリを実行する場合は、非常に高速です。