9


2

C#3のLambda構文では、ワンライナーの無名メソッドを作成するのが非常に便利です。 これらは、C#2で与えられた言葉の多い匿名デリゲート構文に対する明確な改善です。 しかしながら、ラムダの便利さは、それらが提供する関数型プログラミングの意味論を必ずしも必要としない場所でそれらを使用する誘惑をもたらします。

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

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

  • 自分のイベントハンドラを直接呼び出すことはできません。イベントによってのみ引き起こされる可能性があります。 もちろん、これらの単純なイベントハンドラの場合は、直接呼び出す必要があることはほとんどありません。

  • ハンドラーをイベントから外すことはできません。 一方、本当にイベントハンドラのフックを解除する必要があるので、これはあまり問題にはなりません

述べられた理由のために、これら二つの事は私をあまり気にしない。 そして、実際に問題であれば、ラムダをメンバーデリゲートに格納することで、これらの問題を両方とも解決することができますが、それはラムダを利便性のために使用しクラスを雑然としないようにするという目的を損なうことになります。

しかし、他にも2つありますが、それほど明白ではないと思いますが、もっと問題がある可能性があります。

  • 各ラムダ関数は、それを含むスコープの上に_closure_を形成します。 これは、コンストラクタの早い段階で作成された一時オブジェクトは、クロージャがそれらへの参照を維持しているために必要以上に長く存続することを意味します。 うまくいけば、コンパイラーはラムダが使用しないオブジェクトをクロージャから除外するのに十分賢いですが、よくわかりません。 誰か知ってる? 幸いなことに、私はコンストラクタの中で一時オブジェクトを作成することはあまりないので、これは必ずしも問題ではありません。 私がしたシナリオ、そしてラムダの外側でそれを容易に範囲指定することができなかったシナリオを想像することができます。

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

だから私はおそらく他の言語での機能プログラミング機能を持った経験を持つ人たちのアドバイスと経験を利用したいと思います。 このようなことに対して確立されたベストプラクティスはありますか? イベントハンドラや、ラムダがその有効範囲を大幅に上回っているその他の場合には、ラムダを使用しないようにしますか。 そうでない場合、どのしきい値でラムダの代わりに実際の関数を使用すると決めますか? 上記の落とし穴のうちのどれかが誰かをかなり噛んだことがありますか? 考えていなかった落とし穴はありますか?

5 Answer


4


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

  1. イベントパラメータを解凍します。

  2. 適切なパラメータで名前付きメソッドを呼び出します。

これにより、クラスの名前空間が他の目的にうまく使用できないイベントハンドラメソッドで散らかってしまうのを避け、実装するアクションメソッドの必要性と目的について考えることを余儀なくされ、結果としてコードがきれいになりました。


2


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

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

つまり、ラムダ内から包含スコープにアクセスしなければ、クロージャを生成することはありません。

しかし、これはちょっと「聞き取り」であり、C#コンパイラに精通している人にそのことを考慮してもらいたいです。

それだけで、古いC#2.0の匿名デリゲート構文も同じことをしていて、私はほぼ常に短いイベントハンドラに匿名デリゲートを使っています。

イベントハンドラのフックを解除する必要がある場合は匿名のメソッドを使用しないでください。それ以外の場合は私がすべてです。


2


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

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

ですから、コンパイラは十分スマートであるというあなたの仮定は正しいものです。


1


ラムダの同じ特性のほとんどは、あなたがそれらを使用することができる他の場所でも同様に適用できます。 イベントハンドラがそれらのための場所ではない場合、私はこれ以上考えられません。 それはその一点に位置する論理の一点自立型ユニットです。

多くの場合、このイベントは手元の仕事にちょうど適していることが判明したコンテキストの小さなパッケージを取得するように設計されています。

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


0


ラムダに関しては、https://stackoverflow.com/questions/23020789/does-passing-a-method-of-one-object-to-another-object-keep-the-first-object-aliv [この質問]私が尋ねた最近では、受け入れられた回答の中でオブジェクトの寿命への影響についていくつかの関連事実があります。

私が最近学んだもう一つの興味深いことは、C#コンパイラが捕捉して生き続けることに関して、単一のクロージャと同じ範囲の複数のクロージャを理解することです。 残念ながら、この元のソースが見つかりません。 もう一度つまずいたらそれを付け加えます。

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

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

パブリッククラスUserIsUnhappy:Notification {public int unhappiness; }