web-dev-qa-db-ja.com

Func <T>ではなくExpression <Func <T>>を使うのはなぜでしょうか。

私はラムダとFuncActionのデリゲートを理解しています。しかし、表現は私を切り詰めます。どのような状況で、普通のExpression<Func<T>>ではなくFunc<T>を使用しますか?

854
Richard Nagle

ラムダ式を式ツリーとして扱い、実行する代わりにそれらの中を調べたい場合。たとえば、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を返す式を記述するデータ構造に変換されます。

Expression vs Func より大きな画像

どちらもコンパイル時に同じように見えますが、コンパイラーが生成するものはまったく異なります

1079
Mehrdad Afshari

私がそれがどれほど単純であるかに気づくまで、これらの答えが私の頭の上に見えたので、私は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が返されます。言い換えれば、気づかないうちに、あなたはあなたのデータセットを問い合わせるものとは対照的に繰り返されるリストに変えました。あなたが本当に署名の下でフードの下を見るまで、違いに気付くのは難しいです。

276
Chad Hedgcock

Expression vs Funcを選択する際の非常に重要な考慮事項は、LINQ to EntitiesのようなIQueryableプロバイダはExpressionで渡したものを「ダイジェスト」できますが、Funcで渡したものは無視されることです。この件に関して2つのブログ記事があります。

エンティティフレームワークでのExpressionとFuncの詳細 および LINQと恋に落ちる - 第7部:式とFuncs (最後のセクション)

96
LSpencer777

Func<T>Expression<Func<T>>の違いについてのメモをいくつか追加します。

  • Func<T>は単なる普通の昔のMulticastDelegateです。
  • Expression<Func<T>>は、式ツリーの形式でのラムダ式の表現です。
  • 式ツリーは、ラムダ式構文またはAPI構文を介して作成できます。
  • 式ツリーはデリゲートFunc<T>にコンパイルできます。
  • 逆変換は理論的には可能ですが、逆コンパイルのようなものです。単純なプロセスではないため、組み込み機能はありません。
  • 式ツリーはExpressionVisitorを通して観察/翻訳/修正することができます。
  • iEnumerableの拡張メソッドはFunc<T>で動作します。
  • iQueryableの拡張メソッドはExpression<Func<T>>で動作します。

コードサンプルを使用して詳細を説明した記事があります。
LINQ:Func T対Expression <Func T>

それが役立つことを願っています。

69

それについてのより哲学的な説明がKrzysztof Cwalinaの本( フレームワークデザインガイドライン:慣習、慣用句、および再利用可能な.NETライブラリのパターン )からあります。

Rico Mariani

非画像バージョン用に編集:

ほとんどの場合、コードを実行するだけでFuncまたはActionが必要になります。コードの分析、シリアル化が必要な場合はExpressionが必要です。 Expressionはコードを考えるためのもので、Func/Actionはそれを実行するためのものです。

60
Oğuzhan Soykan

LINQは標準的な例です(たとえば、データベースとの対話など)が、実際には実際に行うのではなく、 what を表現することに関心がある場合はいつでも。たとえば、 protobuf-net のRPCスタックでこのアプローチを使用します(コード生成などを避けるため)。したがって、次のようにメソッドを呼び出します。

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

これは式ツリーを分解してSomeMethod(および各引数の値)を解決し、RPC呼び出しを実行し、すべてのref/out argsを更新して、リモート呼び出しからの結果を返します。これは式ツリーによってのみ可能です。私はこれをもっとカバーします ここ

もう1つの例は、 総称演算子 codeのように、ラムダにコンパイルする目的で式ツリーを手動で構築している場合です。

36
Marc Gravell

関数をコードではなくデータとして扱う場合は、式を使用します。コードを(データとして)操作したい場合は、これを実行できます。ほとんどの場合、式が不要な場合は、おそらく式を使用する必要はありません。

19
Andrew Hare

主な理由は、コードを直接実行するのではなく、調べたい場合です。これにはいくつかの理由が考えられます。

  • コードを別の環境にマッピングする(Entity FrameworkのC#コードをSQLに変換する)
  • 実行時にコードの一部を置き換える(動的プログラミング、あるいは普通のDRY技法まで)
  • コード検証(スクリプトをエミュレートするときや分析を行うときに非常に役立ちます)
  • 直列化 - 式はかなり簡単かつ安全に直列化できますが、デリゲートはできません
  • 本質的に厳密に型付けされていないことに対する厳密に型付けされた安全性、および実行時に動的呼び出しを行っていてもコンパイラチェックを活用する(Razorを備えたASP.NET MVC 5が良い例です)
16
Luaan

パフォーマンスについて言及している答えはまだありません。 Func<>sをWhere()またはCount()に渡すのは良くありません。本当に悪い。 Func<>を使用すると、IEnumerableではなくIQueryable LINQを呼び出します。つまり、テーブル全体が取り込まれ、 その後 フィルタ処理されます。 Expression<Func<>>は、特にあなたが別のサーバにあるデータベースを問い合わせているなら、かなり速いです。

9
mhenry1384