私はラムダとFunc
とAction
のデリゲートを理解しています。しかし、表現は私を切り詰めます。どのような状況で、普通のExpression<Func<T>>
ではなくFunc<T>
を使用しますか?
ラムダ式を式ツリーとして扱い、実行する代わりにそれらの中を調べたい場合。たとえば、LINQ to SQLは式を取得し、それを同等のSQLステートメントに変換して、(ラムダを実行するのではなく)サーバーに送信します。
概念的に、Expression<Func<T>>
はFunc<T>
とは完全に異なります。 Func<T>
はメソッドへのポインタであるdelegate
を示し、Expression<Func<T>>
はラムダ式のツリーデータ構造を示します。このツリー構造は、実際のことを行うのではなく、ラムダ式の動作を説明しています。基本的に、式、変数、メソッド呼び出しなどの構成に関するデータを保持します(たとえば、このラムダが定数+パラメータであるなどの情報を保持します)。この説明を使用して、(Expression.Compile
を使用して)実際のメソッドに変換したり、他の処理(LINQ to SQLの例など)を行ったりできます。ラムダを匿名のメソッドおよび式ツリーとして扱う行為は、純粋にコンパイル時のものです。
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
何も取得せず、10を返すILメソッドに効果的にコンパイルされます。
Expression<Func<int>> myExpression = () => 10;
パラメータを取得せず、値10を返す式を記述するデータ構造に変換されます。
どちらもコンパイル時に同じように見えますが、コンパイラーが生成するものはまったく異なります。
私がそれがどれほど単純であるかに気づくまで、これらの答えが私の頭の上に見えたので、私はno-for-answerを追加しています。複雑になっているために「頭を包む」ことができなくなることが予想される場合があります。
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
を必要とし、Func
が適切でない理由は明らかです。 Func
は、それをSQL/MongoDb/otherクエリに変換する方法の重要性を理解するために、それ自体を取得する方法を持ち合わせていません。あなたはそれが減算で足し算または掛け算をしているかどうかを見ることができません。実行できるのはそれだけです。一方、Expression
を使用すると、デリゲートの内部を調べて実行したいことすべてを確認でき、SQLクエリーのように必要なものに変換できます。私のDbContextは実際にラムダ式の中にあるものをSQLに変換することを盲目にしていたのでFunc
はうまくいきませんでした。
編集:ジョンピーターの要求で私の最後の文を解説:
IQueryableはIEnumerableを拡張するので、Where()
のようなIEnumerableのメソッドはExpression
を受け入れるオーバーロードを取得します。 Expression
を渡すと、結果としてIQueryableが保持されますが、Func
を渡すと、ベースのIEnumerableに戻り、結果としてIEnumerableが返されます。言い換えれば、気づかないうちに、あなたはあなたのデータセットを問い合わせるものとは対照的に繰り返されるリストに変えました。あなたが本当に署名の下でフードの下を見るまで、違いに気付くのは難しいです。
Expression vs Funcを選択する際の非常に重要な考慮事項は、LINQ to EntitiesのようなIQueryableプロバイダはExpressionで渡したものを「ダイジェスト」できますが、Funcで渡したものは無視されることです。この件に関して2つのブログ記事があります。
エンティティフレームワークでのExpressionとFuncの詳細 および LINQと恋に落ちる - 第7部:式とFuncs (最後のセクション)
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> 。
それが役立つことを願っています。
LINQは標準的な例です(たとえば、データベースとの対話など)が、実際には実際に行うのではなく、 what を表現することに関心がある場合はいつでも。たとえば、 protobuf-net のRPCスタックでこのアプローチを使用します(コード生成などを避けるため)。したがって、次のようにメソッドを呼び出します。
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
これは式ツリーを分解してSomeMethod
(および各引数の値)を解決し、RPC呼び出しを実行し、すべてのref
/out
argsを更新して、リモート呼び出しからの結果を返します。これは式ツリーによってのみ可能です。私はこれをもっとカバーします ここ 。
もう1つの例は、 総称演算子 codeのように、ラムダにコンパイルする目的で式ツリーを手動で構築している場合です。
関数をコードではなくデータとして扱う場合は、式を使用します。コードを(データとして)操作したい場合は、これを実行できます。ほとんどの場合、式が不要な場合は、おそらく式を使用する必要はありません。
主な理由は、コードを直接実行するのではなく、調べたい場合です。これにはいくつかの理由が考えられます。
パフォーマンスについて言及している答えはまだありません。 Func<>
sをWhere()
またはCount()
に渡すのは良くありません。本当に悪い。 Func<>
を使用すると、IEnumerable
ではなくIQueryable
LINQを呼び出します。つまり、テーブル全体が取り込まれ、 その後 フィルタ処理されます。 Expression<Func<>>
は、特にあなたが別のサーバにあるデータベースを問い合わせているなら、かなり速いです。