サーチ…
前書き
イベントとは、何かが発生したこと(マウスクリックなど)や、場合によっては(価格変更など)起こりそうなことの通知です。
クラスはイベントを定義することができ、そのインスタンス(オブジェクト)はこれらのイベントを発生させることがあります。たとえば、Buttonには、ユーザーがクリックしたときに発生するClickイベントが含まれている場合があります。
イベントハンドラは、対応するイベントが発生したときに呼び出されるメソッドです。フォームには、ボタンに含まれるすべてのボタンのClickedイベントハンドラが含まれています。
パラメーター
パラメータ | 詳細 |
---|---|
EventArgsT | EventArgsから派生し、イベントパラメータを含む型。 |
イベント名 | イベントの名前。 |
ハンドラー名 | イベントハンドラの名前。 |
SenderObject | イベントを呼び出すオブジェクト。 |
EventArguments | イベントパラメータを含むEventArgsT型のインスタンス。 |
備考
イベントを起こすとき:
- デリゲートが
null
かどうかを常にチェックしnull
。 nullデリゲートは、イベントにサブスクライバがないことを意味します。サブスクライバのないイベントを発生させると、NullReferenceException
が発生します。
- nullを確認する前に、デリゲート(eg
EventName
)をローカル変数(eventName
)にコピーして、イベントを発生させます。これにより、マルチスレッド環境での競合状態が回避されます。
間違っている :
if(Changed != null) // Changed has 1 subscriber at this point
// In another thread, that one subscriber decided to unsubscribe
Changed(this, args); // `Changed` is now null, `NullReferenceException` is thrown.
右 :
// Cache the "Changed" event as a local. If it is not null, then use
// the LOCAL variable (handler) to raise the event, NOT the event itself.
var handler = Changed;
if(handler != null)
handler(this, args);
-
if
ステートメント内のサブスクライバのヌルチェックではなく、ヌル条件付き演算子(?)を使用してメソッドをEventName?.Invoke(SenderObject, new EventArgsT());
:EventName?.Invoke(SenderObject, new EventArgsT());
- Action <>を使用してデリゲート型を宣言する場合、匿名メソッド/イベントハンドラのシグネチャは、イベント宣言の宣言された匿名デリゲート型と同じである必要があります。
イベントの宣言と募集
イベントの宣言
次の構文を使用して、任意のclass
またはstruct
イベントを宣言できます。
public class MyClass
{
// Declares the event for MyClass
public event EventHandler MyEvent;
// Raises the MyEvent event
public void RaiseEvent()
{
OnMyEvent();
}
}
イベントの宣言のための拡張構文があり、イベントのプライベートインスタンスを保持し、 add
およびset
アクセサを使用してパブリックインスタンスを定義します。構文はC#のプロパティと非常によく似ています。コンパイラは、複数のスレッドがクラスのイベントにイベントハンドラを安全に追加および削除できるようにするコードを生成するため、上記の構文を優先する必要があります。
イベントの開催
private void OnMyEvent()
{
EventName?.Invoke(this, EventArgs.Empty);
}
private void OnMyEvent()
{
// Use a local for EventName, because another thread can modify the
// public EventName between when we check it for null, and when we
// raise the event.
var eventName = EventName;
// If eventName == null, then it means there are no event-subscribers,
// and therefore, we cannot raise the event.
if(eventName != null)
eventName(this, EventArgs.Empty);
}
イベントは、宣言型によってのみ呼び出すことができます。クライアントは購読/購読解除しかできません。
EventName?.Invoke
がサポートされていない6.0以前のC#バージョンでは、この例に示すように、起動前に一時変数にイベントを割り当てて、複数のスレッドが同じものを実行する場合にスレッドの安全を確保することをおEventName?.Invoke
コード。そうしないと、複数のスレッドが同じオブジェクトインスタンスを使用している特定の状況で、 NullReferenceException
がスローされることがあります。 C#6.0では、コンパイラはC#6のコード例に示すようなコードを出力します。
標準イベント宣言
イベント宣言:
public event EventHandler<EventArgsT> EventName;
イベントハンドラの宣言:
public void HandlerName(object sender, EventArgsT args) { /* Handler logic */ }
イベントを購読する:
動的に:
EventName += HandlerName;
デザイナーを通して:
- コントロールのプロパティウィンドウ(ライトニングボルト)の[イベント]ボタンをクリックします。
- イベント名をダブルクリックします:
- Visual Studioはイベントコードを生成します:
private void Form1_Load(object sender, EventArgs e)
{
}
メソッドを呼び出す:
EventName(SenderObject, EventArguments);
匿名イベントハンドラ宣言
イベント宣言:
public event EventHandler<EventArgsType> EventName;
ラムダ演算子=>を使用してイベントに登録するイベントハンドラの宣言:
EventName += (obj, eventArgs) => { /* Handler logic */ };
デリゲート匿名メソッド構文を使用したイベントハンドラ宣言:
EventName += delegate(object obj, EventArgsType eventArgs) { /* Handler Logic */ };
イベントのパラメータを使用しないイベントハンドラの宣言とサブスクリプションです。したがって、パラメータを指定することなく上記の構文を使用できます。
EventName += delegate { /* Handler Logic */ }
イベントを呼び出す:
EventName?.Invoke(SenderObject, EventArguments);
非標準イベント宣言
イベントは、 EventHandler
およびEventHandler<T>
だけでなく、任意のデリゲート型にすることができます。例えば:
//Declaring an event
public event Action<Param1Type, Param2Type, ...> EventName;
これは、標準のEventHandler
イベントと同様に使用されます。
//Adding a named event handler
public void HandlerName(Param1Type parameter1, Param2Type parameter2, ...) {
/* Handler logic */
}
EventName += HandlerName;
//Adding an anonymous event handler
EventName += (parameter1, parameter2, ...) => { /* Handler Logic */ };
//Invoking the event
EventName(parameter1, parameter2, ...);
同じタイプの複数のイベントをフィールドやローカル変数と同様に1つのステートメントに宣言することは可能です(ただし、これはしばしば悪い考えです)。
public event EventHandler Event1, Event2, Event3;
これは、 EventHandler
型の3つの別々のイベント( Event1
、 Event2
、およびEvent3
)を宣言します。
注意:いくつかのコンパイラはインタフェースとクラスでこの構文を受け入れるかもしれませんが、C#の仕様(v5.0§13.2.3)はインタフェースを許可しないインタフェースの文法を提供しているため、インタフェースでの使用は異なるコンパイラでは信頼できません。
追加データを含むカスタムEventArgを作成する
カスタムイベントは通常、イベントに関する情報を含むカスタムイベント引数を必要とします。たとえば、 MouseDown
イベントやMouseUp
イベントなどのマウスイベントで使用されるMouseEventArgs
には、イベントの生成に使用されたLocation
またはButtons
に関する情報が含まれています。
新しいイベントを作成するときに、カスタムイベントargを作成するには:
-
EventArgs
から派生したクラスを作成し、必要なデータのプロパティを定義します。 - 規約として、クラスの名前は
EventArgs
終わる必要があります。
例
以下の例では、クラスのPrice
プロパティのPriceChangingEventArgs
イベントを作成します。イベントデータクラスには、 CurrentPrice
とNewPrice
が含まれています。このイベントは、新しい値をPrice
プロパティに割り当てると消費者に値が変化していることを知らせ、現在の価格と新しい価格について知らせるときに発生します。
PriceChangingEventArgs
public class PriceChangingEventArgs : EventArgs
{
public PriceChangingEventArgs(int currentPrice, int newPrice)
{
this.CurrentPrice = currentPrice;
this.NewPrice = newPrice;
}
public int CurrentPrice { get; private set; }
public int NewPrice { get; private set; }
}
製品
public class Product
{
public event EventHandler<PriceChangingEventArgs> PriceChanging;
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = value;
}
}
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PriceChanging;
if (handler != null)
handler(this, e);
}
}
消費者が新しい値を変更できるようにして、この例を拡張すると、その値がプロパティに使用されます。これを行うには、これらの変更をクラスに適用すれば十分です。
NewPrice
の定義を変更可能に変更します。
public int NewPrice { get; set; }
e.NewPrice
を呼び出した後、 OnPriceChanging
をプロパティの値として使用するためにPrice
の定義を変更します。
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = e.NewPrice;
}
}
キャンセル可能なイベントの作成
キャンセル可能なイベントは、クラスのFormClosing
イベントなど、キャンセル可能なアクションを実行しようとしているときに発生させることができForm
。
このようなイベントを作成するには:
-
CancelEventArgs
から派生した新しいイベント引数を作成し、イベントデータのプロパティを追加します。 -
EventHandler<T>
を使用してイベントを作成し、作成した新しいキャンセルイベントargクラスを使用します。
例
以下の例では、クラスのPrice
プロパティのPriceChangingEventArgs
イベントを作成します。イベントデータクラスには、消費者が新しいものについて知ることができるValue
が含まれています。このイベントは、新しい値をPrice
プロパティに割り当てると消費者に値が変更されていることを通知し、イベントをキャンセルさせるときに発生します。消費者がイベントをキャンセルした場合、以前のPrice
値が使用されます。
PriceChangingEventArgs
public class PriceChangingEventArgs : CancelEventArgs
{
int value;
public int Value
{
get { return value; }
}
public PriceChangingEventArgs(int value)
{
this.value = value;
}
}
製品
public class Product
{
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(value);
OnPriceChanging(e);
if (!e.Cancel)
price = value;
}
}
public event EventHandler<PriceChangingEventArgs> PropertyChanging;
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PropertyChanging;
if (handler != null)
PropertyChanging(this, e);
}
}
イベントのプロパティ
クラスがイベントの数を大きくした場合、デリゲートごとに1つのフィールドのストレージコストは受け入れられない可能性があります。 .NET Frameworkは、これらのケースのイベントプロパティを提供します。この方法で、 EventHandlerList
などの別のデータ構造を使用して、イベントデリゲートを格納できます。
public class SampleClass
{
// Define the delegate collection.
protected EventHandlerList eventDelegates = new EventHandlerList();
// Define a unique key for each event.
static readonly object someEventKey = new object();
// Define the SomeEvent event property.
public event EventHandler SomeEvent
{
add
{
// Add the input delegate to the collection.
eventDelegates.AddHandler(someEventKey, value);
}
remove
{
// Remove the input delegate from the collection.
eventDelegates.RemoveHandler(someEventKey, value);
}
}
// Raise the event with the delegate specified by someEventKey
protected void OnSomeEvent(EventArgs e)
{
var handler = (EventHandler)eventDelegates[someEventKey];
if (handler != null)
handler(this, e);
}
}
このアプローチは、WinFormsなどのGUIフレームワークで広く使用されています。このフレームワークでは、コントロールには数十件のイベントが含まれます。
EventHandlerList
はスレッドセーフではないので、クラスを複数のスレッドから使用することを期待する場合は、ロックステートメントやその他の同期メカニズムを追加する必要があります(またはスレッドの安全性を提供するストレージを使用する必要があります)。