9

C#3のLambda構文を使用すると、ワンライナーの無名メソッドを作成するのに非常に便利です。これらは、C#2が提供してくれたより言葉の多い匿名デリゲート構文を確実に改善したものです。ただし、ラムダの利便性は、ラムダが提供する関数型プログラミングのセマンティクスを必ずしも必要としない場所でラムダを使用したいという誘惑をもたらします。

たとえば、私のイベントハンドラーは、状態値を設定したり、別の関数を呼び出したり、別のオブジェクトにプロパティを設定したりする単純なワンライナーである(または少なくとも最初は)ことがよくあります。私のクラスにはさらに別の単純な関数がありますか、それともコンストラクターのイベントにラムダを詰め込む必要がありますか?

このシナリオでは、ラムダには明らかな欠点がいくつかあります。

  • イベントハンドラーを直接呼び出すことはできません。イベントによってのみトリガーできます。もちろん、これらの単純なイベントハンドラーの場合、直接呼び出す必要がある時間はほとんどありません
  • イベントからハンドラーのフックを解除できません。一方、イベントハンドラーのフックを解除する必要があることはめったにないので、とにかく、これはそれほど問題にはなりません

述べられた理由のために、これらの2つのことは私をあまり気にしません。そして、ラムダをメンバーデリゲートに格納することで、これらの問題の両方が本当に問題である場合は解決できますが、ラムダを便利に使用し、クラスを乱雑にしないという目的に反することになります。

他に2つのことがありますが、それほど明白ではないかもしれませんが、おそらくもっと問題があると思います。

  • 各ラムダ関数は、それを含むスコープに対してクロージャーを形成します。これは、コンストラクターで以前に作成された一時オブジェクトが、それらへの参照を維持するクロージャのために、必要以上に長く存続することを意味する可能性があります。うまくいけば、コンパイラはラムダが使用しないクロージャからオブジェクトを除外するのに十分賢いですが、私にはわかりません。誰か知っていますか?

    幸いなことに、コンストラクターで一時オブジェクトを作成することはあまりないため、これが常に問題になるとは限りません。ただし、私が行ったシナリオと、ラムダの外で簡単にスコープを設定できないシナリオを想像することができます。

  • 保守性が低下する可能性があります。ビッグタイム。関数として定義されているイベントハンドラーとラムダとして定義されているイベントハンドラーがある場合、バグの追跡やクラスの理解が難しくなるのではないかと心配しています。その後、イベントハンドラーが拡張された場合は、それらをクラスレベルの関数に移動するか、コンストラクターにクラスの機能を実装するコードが大量に含まれているという事実に対処する必要があります。 。

ですから、他の人、おそらく関数型プログラミング機能を備えた他の言語の経験を持つ人のアドバイスと経験を利用したいと思います。この種のことについて確立されたベストプラクティスはありますか?イベントハンドラーで、またはラムダがその囲んでいるスコープよりも大幅に長生きするその他の場合にラムダを使用することを避けますか?そうでない場合、ラムダの代わりに実際の関数を使用することを決定するしきい値はどれですか?上記の落とし穴のいずれかが誰かを著しく噛んだことがありますか?思いもよらなかった落とし穴はありますか?

4

5 に答える 5

4

私は通常、イベントハンドラーの配線専用のルーチンを1つ持っています。そこでは、実際のハンドラーに匿名デリゲートまたはラムダを使用し、可能な限り短くします。これらのハンドラーには2つのタスクがあります。

  1. イベントパラメータを解凍します。
  2. 適切なパラメータを使用して名前付きメソッドを呼び出します。

これにより、他の目的にきれいに使用できないイベントハンドラーメソッドでクラスの名前空間が乱雑になるのを避け、実装するアクションメソッドのニーズと目的について考える必要があり、一般的にコードがクリーンになりました。

于 2008-12-21T18:19:07.747 に答える
2

各ラムダ関数は、それを含むスコープに対してクロージャーを形成します。これは、コンストラクターで以前に作成された一時オブジェクトが、それらへの参照を維持するクロージャのために、必要以上に長く存続することを意味する可能性があります。うまくいけば、コンパイラはラムダが使用しないクロージャからオブジェクトを除外するのに十分賢いですが、私にはわかりません。誰か知っていますか?

私が読んだことから、C#コンパイラは、含まれているスコープを閉じる必要があるかどうかに応じて、匿名メソッドまたは匿名内部クラスのいずれかを生成します。

つまり、ラムダ内から包含スコープにアクセスしない場合、クロージャーは生成されません。

ただし、これはちょっとした「伝聞」であり、C#コンパイラに精通している人に検討してもらいたいと思います。

そうは言っても、古いC#2.0の匿名デリゲート構文は同じことを行い、私はほとんどの場合、短いイベントハンドラーに匿名デリゲートを使用していました。

さまざまな長所と短所について十分に説明しました。イベントハンドラーのフックを解除する必要がある場合は、匿名メソッドを使用しないでください。それ以外の場合は、私がすべてです。

于 2008-12-21T18:16:39.903 に答える
2

コンパイラーでの少しの実験に基づいて、コンパイラーはクロージャーを作成するのに十分賢いと思います。私が行ったのは、List.Find()の述語に使用される2つの異なるラムダを持つ単純なコンストラクターでした。

最初のlamdbaはハードコードされた値を使用し、2番目はコンストラクターのパラメーターを使用しました。最初のラムダは、クラスのプライベート静的メソッドとして実装されました。2番目のラムダは、クロージングを実行するクラスとして実装されました。

したがって、コンパイラが十分に賢いというあなたの仮定は正しいです。

于 2008-12-21T18:33:04.973 に答える
1

ラムダの同じ特性のほとんどは、それらを使用できる他の場所にも同様に適用できます。イベントハンドラーが彼らのための場所でなければ、私はこれ以上のことは考えられません。これは、単一ポイントに配置された、単一ポイントの自己完結型ロジックユニットです。

多くの場合、このイベントは、目前の仕事にぴったりのコンテキストの小さなパッケージを取得するように設計されています。

これはリファクタリングの意味での「いい匂い」のひとつだと思います。

于 2008-12-21T18:22:20.843 に答える
0

ラムダに関して、私が最近尋ねたこの質問には、受け入れられた回答のオブジェクトの寿命への影響に関するいくつかの関連する事実があります。

私が最近学んだもう 1 つの興味深いことは、C# コンパイラは、キャプチャして保持するものに関して、単一のクロージャと同じスコープ内の複数のクロージャを理解するということです。悲しいことに、これの元のソースを見つけることができません。また見つけたら追記します。

個人的には、ラムダをイベント ハンドラーとして使用していません。ロジックが要求から結果に流れるときに、読みやすさの利点が実際に得られると感じているからです。イベント ハンドラーは、コンストラクターまたは初期化子に追加される傾向がありますが、オブジェクトのライフサイクルのこの時点で呼び出されることはめったにありません。では、なぜ私のコンストラクターは、実際にはずっと後に起こっていることを今やっているように読まなければならないのでしょうか?

一方、私は全体的に少し異なる種類のイベント メカニズムを使用しています。これは、C# 言語機能よりも好ましいと思います: C# で書き直された iOS スタイルの NotificationCenter で、Type (Notification から派生) によってキー付けされたディスパッチ テーブルと、アクション < 通知 > 値。これにより、次のように、単一行の「イベント」タイプ定義が可能になります。

public class UserIsUnhappy : Notification { public int unhappiness; }
于 2014-04-15T11:00:57.147 に答える