.Netでのイベントハンドラーの設定/削除
-
03-07-2019 - |
質問
だから私は別のプログラマーのコードを修正/維持することにこだわっています(blech)
私はルールの確固たる教授です"それが壊れていないなら、それを修正しないでください!" ので、恐ろしいコードに出くわすたびに何かを変えたいと思うので、必要な修正を行うために可能な限り最小限のコードの変更のみに制限されています。しかし、場合によっては、フォロー/変更を試みる前に何かを本当に理解する必要があります。
ここで少し出会いました:
region.LineSelected = (x) => { };
それがこれと同じかどうか疑問に思っています:
region.LineSelected = null;
メソッドを変更する前に、最初の行が何をしているかについて100%肯定的になりたい。
解決
テーマに関する現在の意見に基づいて編集
これらは同じではありません。ラムダバージョンは、空の匿名メソッドにイベントハンドラーを追加しています。これにより、他のコードはLineSelected()がnullであるかどうかを心配せずに自由に発生させることができます(つまり、リスナーがない)。
例:
var lineSelected = this.LineSelected;
if (lineSelected != null)
{
lineSelected(EventArgs.Empty);
}
ifの後、ただしイベントが発生する前に別のスレッドでLineSelectedから何かがサブスクライブを解除した場合、上記のステートメントはNullReferenceExceptionをスローできます。 LineSelectedを一時変数に割り当ててから、サブスクライブされていないイベントリスナーを呼び出すことができます。イベントハンドラーをローカル変数に割り当てることは、nullデリゲートを処理する推奨方法です。
空のデリゲートを追加することにより、他のコードは常にNullReferenceExceptionを恐れることなくLineSelectedを呼び出すことができます。マルチキャストイベントデリゲートをローカル変数に割り当てることにより、値が別のスレッドによって変更されました。
他のヒント
最初のコード行がある理由は考えられません。私が考えることができる唯一のことは、LineSelectedイベントを発生させるときです。クラスの最初のコード行がある場合、LineSelectedイベントがnullであるかどうかを確認する必要はありません。例:
if (this.LineSelected != null)
{
LineSelected(this,new EventArgs());
}
代わりに、nullチェックなしでイベントを発生させることができます。
ただし、コードの2行目では、nullを確認する必要があります。
これはイベントハンドラではなく、単純なデリゲートです。 (イベントをアタッチおよびデタッチするには、イベントハンドラーを+ =および-=で変更する必要があります)。
前述のように、デリゲートプロパティを空のハンドラに設定すると、デリゲートを呼び出す前にnullチェックを実行する必要がなくなります(他に何もnullに設定できないと仮定)。
これにより、コードの呼び出しがより便利になりますが、パフォーマンスの向上と混同しないでください(nullをチェックする必要がなくなります)。一般的に、デリゲートメソッド呼び出しのオーバーヘッドは、nullチェックのオーバーヘッドよりも大幅に高くなります。 nullチェックを回避するとパフォーマンスが向上するシナリオがいくつかある場合があります(たとえば、デリゲートに「実際の」実装が99.99%ある場合など)また、デリゲート呼び出しを完全に削除して、より効率的な何かを支持することを保証しない価値があると思います。
これは、すべてのイベントでnullチェックを回避する手法だと思います。
LineSelectedイベント発生コードに適切なnullチェックがない場合、これにより例外が発生します。
region.LineSelected = null;
/* no event handlers added to LineSelected */
class Region {
void OnLineSelected() {
// Null error!
LineSelected();
}
}
ただし、空のno-sideエフェクトハンドラーが追加されている場合、イベントにハンドラーが追加されていなくても、上記のコードは正常に機能します。常に空のハンドラーが接続されているためです。
リチャードとアンドリューが言ったことを拡張するために、それは同等のものです
region.LineSelected = delegate {};
これは、イベントを発生させるときに、デリゲートが含まれているため(最初にnullをチェックする必要がないことを意味します(パフォーマンスヒットはわずかですが))
いいえ、同じことではありません-最初の行は、 LineSelected
に null
とは非常に異なる空のデリゲートを割り当てます。
違いを見つける最も簡単な方法は、ラムダ構文を使用するときにコンパイラーがユーザーに代わって生成するコードを調べることです。このコード:
using System;
class Program
{
static void Main()
{
Action<int> func0 = (x) => { };
Action<int> func1 = null;
}
}
本当にこれにコンパイルします:
internal class Program
{
// Methods
private static void Main()
{
Action<int> func0 = delegate (int x) {
};
}
}
null
に設定され、他の場所で参照されていないため、コンパイラは func1
を削除するのに十分なほど賢いことに注意してください。ただし、 func0
はまだ残っており、 null
とは非常に異なるが、何もしないデリゲートに設定されていることに注意してください。
これらは同じではありません。イベントハンドラが設定されているためです。
LineSelectedを公開するクラスを言うと、次のことを忘れました:
if( LineSelected != null )
LineSelected(...)
そのクラスがLineSelectedを呼び出し、誰もリッスンしていない場合、NullReferenceExceptionがスローされます
競合状態を回避するために(領域内で)行うこともできることに注意してください:
var event = LineSelected; if(event!= null) イベント(...