C# のイベントは、ちょっと面白いものです。これらは自動プロパティに非常によく似ていますが、プライベートな get メソッドとパブリック (または選択したアクセス) の set メソッドを使用します。
実演させてください。架空のイベントを持つ架空のクラスを作成しましょう。
class SomeObject{
public event EventHandler SomeEvent;
public void DoSomeStuff(){
OnSomeEvent(EventArgs.Empty);
)
protected virtual void OnSomeEvent(EventArgs e){
var handler = SomeEvent;
if(handler != null)
handler(this, e);
}
}
このクラスは、イベントを公開するクラスの典型的なパターンに従います。イベントは公開されますが、保護された仮想「On...」メソッドがあり、デフォルトでは単にイベントを呼び出します (サブスクライバーがある場合)。この保護された仮想メソッドは、イベントを実際に呼び出すロジックをカプセル化するだけでなく、派生クラスに次の方法を提供します。
- より少ないオーバーヘッドでイベントを便利に処理し、
- すべての外部サブスクライバーがイベントを受信する前または後に、何らかの処理を実行します。
- まったく別のイベントを呼び出す、または
- イベントを完全に抑制します。
しかし、SomeEvent と呼ばれるこの「イベント」オブジェクトは何ですか? C# では、フィールド、プロパティ、およびメソッドに精通していますが、イベントとは正確には何なのでしょうか?
これに入る前に、C# には実際にはフィールドとメソッドの 2 種類のクラス メンバーしかないことを理解しておくと役に立ちます。プロパティとイベントは、多かれ少なかれ、それらに加えられた単なる構文糖衣です。
プロパティは、実際には 1 つまたは 2 つのメソッドであり、メタデータに格納された名前であり、C# コンパイラがそれらの 2 つのメソッドのいずれかを参照するために使用できるようにします。つまり、次のようなプロパティを定義すると:
public string SomeProperty{
get{return "I like pie!";}
set{
if(string.Compare(value, "pie", StringComparison.OrdinalIgnoreCase) == 0)
Console.WriteLine("Pie is yummy!");
else Console.WriteLine("\"{0}\" isn't pie!", value ?? "<null>");
}
}
コンパイラは、次の 2 つのメソッドを作成します。
public string get_SomeProperty(){return "I like pie!";}
public void set_SomeProperty(string value){
if(string.Compare(value, "pie", StringComparison.OrdinalIgnoreCase) == 0)
Console.WriteLine("Pie is yummy!");
else Console.WriteLine("\"{0}\" isn't pie!", value ?? "<null>");
}
これは斜めに言っているのではありません。これら 2 つのメソッドは、文字通り、プロパティに関するメタデータのチャンクと共に、コンパイルされたクラスの一部になります。これにより、次にプロパティが読み取られる (get) または書き込まれる (set) ときにどのメソッドを呼び出すかがコンパイラに通知されます。したがって、次のようなコードを書くと:
var foo = someObject.SomeProperty;
someObject.SomeProperty = foo;
コンパイラは に割り当てられたゲッター メソッドとセッター メソッドを見つけ、SomeProperty
コードを次のように変換します。
string foo = someObject.get_SomeProperty();
someObject.set_SomeProperty(foo);
これが、パブリック フィールドを使用してクラスを定義し、後でそれをプロパティに変更して、読み取りまたは書き込み時に何か興味深いことができるようにする場合、この参照を含む外部アセンブリを再コンパイルする必要がある理由です。これは、フィールド アクセス命令がメソッド呼び出し命令になる必要があるためです。
このプロパティは、バッキング フィールドに依存していないという点で、やや異常でした。そのゲッターは定数値を返し、そのセッターはその値をどこにも保存しませんでした。明確にするために、それは完全に有効ですが、ほとんどの場合、次のようにプロパティを定義します。
string someProperty;
public string SomeProperty{get{return someProperty;}set{someProperty = value;}}
このプロパティは、フィールドの読み取りと書き込み以外には何もしません。という名前の public フィールドとほぼ同じですが、クラスのコンシューマを再コンパイルせずに、後でゲッターとセッターにロジックを追加できるSomeProperty
点が異なります。しかし、このパターンは非常に一般的であるため、C# 3 では同じ効果を実現するために「自動プロパティ」が追加されました。
public string SomeProperty{get;set;}
コンパイラはこれを上で書いたのと同じコードに変換しますが、バッキング フィールドにはコンパイラだけが知っている極秘の名前があるため、クラス内であってもコード内のプロパティのみを参照できます。
次のような読み取り専用のプロパティがあるかもしれませんが、バッキング フィールドにはアクセスできないためです。
string someProperty;
public string SomeProperty{get{return someProperty;}}
読み取り専用の自動プロパティが表示されることはほとんどありません (コンパイラでそれらを記述できますが、それらの用途はほとんどありません)。
public string SomeProperty{get;} // legal, but not very useful unless you always want SomeProperty to be null
代わりに、通常は次のように表示されます。
public string SomeProperty{get;private set;}
にアタッチされたprivate
アクセス修飾子set
により、クラス内のメソッドがプロパティを設定できるようになりますが、プロパティはクラス外では読み取り専用として表示されます。
「さて、これは出来事と何の関係があるのですか?」あなたは尋ねるかもしれません。実際のところ、イベントは自動プロパティに非常によく似ています。通常、イベントを宣言すると、コンパイラは超秘密のバッキング フィールドとメソッドのペアを生成します。ただし、バッキング フィールドは極秘ではなく、メソッドのペアは "get" と "set" ではなく、"add" と "remove" です。実演させてください。
次のようなイベントを書くと:
public event EventHandler SomeEvent;
コンパイラが書くものはこれです:
EventHandler SomeEvent;
public void add_SomeEvent(EventHandler value){
SomeEvent = (EventHandler)Delegate.Combine(SomeEvent, value);
}
public void remove_SomeEvent(EventHandler value){
SomeEvent = (EventHandler)Delegate.Remove(SomeEvent, value);
}
また、後で次のようなコードを記述するときに、いくつかのメタデータ グルーが追加されます。
void Awe_SomeEventHandler(object sender, EventArgs e){}
void SomeMethod(SomeObject Awe){
Awe.SomeEvent += Awe_SomeEventHandler
Awe.SomeEvent -= Awe_SomeEventHandler
}
コンパイラはそれを次のように書き換えます(興味深い行のみ):
Awe.add_SomeEvent(Awe_SomeEventHandler);
Awe.remove_SomeEvent(Awe_SomeEventHandler);
ここで注意すべき重要な点は、に関連する公的にアクセス可能なメンバーは add メソッドと remove メソッドだけであり、これらはand演算子SomeEvent
を使用するときに呼び出されるということです。イベントのサブスクライバーを保持する SomeEvent という名前のデリゲート オブジェクトであるバッキング フィールドは、宣言クラスのメンバーのみがアクセスできるプライベート フィールドです。+=
-=
ただし、自動プロパティがバッキング フィールドと getter および setter を手動で記述するためのショートカットに過ぎないのと同じように、デリゲートを明示的に宣言し、メソッドを追加および削除することもできます。
internal EventHandler someEvent;
public event EventHandler SomeEvent{
add{someEvent = (EventHandler)Delegate.Combine(someEvent, value);}
remove{someEvent = (EventHandler)Delegate.Remove(someEvent, value);}
}
次に、アセンブリ内の他のクラスがイベントをトリガーできます。
var handler = Awe.someEvent;
if(handler != null)
handler(Awe, EventArgs.Empty);
ただし、通常の (自動) 方法でイベントを定義し、"Raise" メソッドを公開する方が簡単で慣用的です。
internal void RaiseSomeEvent(){OnSomeEvent(EventArgs.Empty);}
しかし、これで、なぜこのようにしなければならないのか、そしてバックグラウンドで何が起こっているのかが理解できたと思います。