本稿について
この投稿は、C#についてあまり経験のない方がイベントの使い方について理解することを目的としている。
説明の範囲は、
- イベントを使用したプログラミングのメリットを理解する
- イベントを使用したクラスを実現できる
- その際、安全性や拡張性に留意できる
ようになることにある。以下については、説明の対象外になる。
- イベントのアクセサを自分で定義する
- イベントハンドラとしてラムダ式を使用する
また、前提として、
- ジェネリックプログラミング
の知識があるものとしている。
イベントとは
C#におけるイベントとは、一言で言えば、
「デリゲートによるコールバックメカニズムに、安全な購読と購読解除機能を追加するための文法」
ということになるだろう。
そのため、イベントを理解するには、まず、デリゲートやコールバックについておさえておく必要がある。
コールバックとデリゲート
ある処理に対し、処理そのものを渡したいことがある。
int
型の配列を持っているオブジェクトがあるとしよう。
class Hoge
{
private readonly IList<int> _list = new List<int>();
}
この_list
の中には、何かの要素が含まれている。
そして、この_list
内の要素のうち、奇数の要素を取り出す処理を考える。
class Hoge
{
...
public IEnumerable<int> GetOdds()
{
foreach (var n in _list)
if (n % 2 == 1)
yield return n;
}
}
できた。
つづいて、3で割って余りが1になる数だけを取り出す処理を考える。
できたら、次は4で割って余りが1になる数、そしたら、5で割って、6で、7で…………
こうした無限にわきでる要望を、ひとつひとつ別のメソッドを作成して満たすのは不可能である。
そこで、「要素を取り出すかどうかを判定する処理そのもの」を渡すことにしよう。
この「要素を取り出すかどうかを判定する処理」は、int
型の引数を取り、bool
型を返すメソッドになるだろう。
この型のメソッドを渡すには、Func<int, bool>
型の引数を使う。
class Hoge
{
...
// Use LINQ, Luke.
public IEnumerable<int> GetNumbers(Func<int, bool> pred)
{
foreach (var n in _list)
if (pred(n))
yield return n;
}
}
class Xxx
{
static bool Mod3Equals1(int n)
{
return n % 3 == 1;
}
var hoge = new Hoge();
...
var enm = hoge.GetNumbers(Mod3Equals1);
}
さあ、これがコールバックだ。
コールバックとは、ある処理(この例ではGetNumbers
)に対し、そこから呼び出してほしい処理(この例ではMod3Equals1
)を渡すことだ。
GetNumbers
は_list
の内部を先頭から順に取り出し、その要素ごとに与えられた引数であるMod3Equals1
を呼び出す。
これがtrueを返した場合のみ、その値を戻り値の一部として返すという動作をする。
こうした、大きな処理に対して、その一部になる小さな処理を渡す場合もあるし、「ある処理がおこなわれたという通知処理」を渡す場合もある。
ほとんどのイベントが後者の形を実現するために用いられる。また、前者の形をイベントで実現することも可能だ。
話が飛んでしまった。
C#において、こうした「処理を渡す」際に使用する、メソッドへの参照のことをデリゲートと呼ぶ。
C言語の関数ポインタなどと同様、C#のデリゲートもコールバックを実現するための機能になる。
ただ、C#の場合は、オブジェクト指向言語なので、
「静的メソッドなのか、インスタンスメソッドなのか。インスタンスメソッドだとすれば、どのオブジェクトのそれを呼び出すべきなのか」
という情報まで内部で渡している。
デリゲートには、メソッドであれば何でも渡せるわけではなく、引数の数や型、戻り値の型が一致していなければならない。
前の例では、
「int
型の引数を取り、bool
型を返す」
という処理を渡すためにFunc<int, bool>
という型を使用した。
定義済みのデリゲートの型
デリゲートには型があり、渡したい処理の引数の数や型、戻り値の組み合わせごとに別々の型として扱わなければならない。
そして、プログラマがその型を定義できるよう、delegate
構文が用意されている。
しかし、.NET Frameworkにはデリゲートの型が既にいくつも定義されている。
どんなものがあるかは、以下を参考にして欲しい。
http://www.atmarkit.co.jp/fdotnet/dotnettips/730dlgttype/dlgttype.html
この情報の
void Action<T>(T obj)
という形式は
「Action<T>
型というデリゲートの型は、T
型の引数ひとつを取り、戻り値がない」
という意味になる。
こうした型がいくつも用意されているおかげで、プログラマがデリゲートの型を新たに定義する必要は少なくなっている。
なるべく標準で用意されているデリゲートの型を使用しよう。
デリゲートによる通知の実現
運がいいと労働したときに昇給してもらえるProgrammer
クラスを考える。このProgrammer
は昇給するとその情報をうっかり外へ通知する。
通常は、昇給イベントを用意するのだが、今回はデリゲートだけを使用して、昇給を通知することを考えよう。
class Programmer
{
public Action GetPayRise;
private readonly System.Random _Random = new System.Random();
public Programmer(int id) { ... }
public void Work(int quantity)
{
...
if (_Random.Next(1000) == 0)
if (GetPayRise != null)
GetPayRise();
}
}
Action
は、引数なし、戻り値なしの処理を渡すためのデリゲートの型だ。
ここでは、この型の変数(GetPayRise
フィールド)を公開することで、他のクラスからその参照を登録することを可能にしている。
そして、このGetPayRise
に()
をつけることで、登録された処理を呼び出すことができる。事前にnullチェックしているのは、未登録の場合はこの値はnullになって処理の呼び出しが例外を引き起こすからだ。
これで、「とても高い確率」で昇給し、このとき、GetPayRise
に登録されているデリゲートを呼び出す仕組みができた。
では、昇給が通知されたときに呼び出される処理を追加してみよう。
class Leader
{
private Programmer _Programmer101 = MyCompany.GetProgrammer(101);
private int _WorkQuantityForProgrammer101 = 10;
void Programmer101_GetPayRise()
{
_WorkQuantityForProgrammer101++;
}
public Leader()
{
_Programmer101.GetPayRise = Programmer101_GetPayRise;
}
public void DailyProcess()
{
_Programmer101.Work(_WorkQuantityForProgrammer101);
ReadNewsPaper();
}
}
このLeader
は、昇給を通知されると、彼への仕事量を増やす処理をおこなう。
コンストラクタの中で_Programmer101
のGetPayRise
変数に自分の処理(Programmer101_GetPayRise
)を登録している。
これで、Programmer101
のWork
を呼び出せば、ごく稀に昇給が通知され、彼への仕事量を増やすことができるようになった。
こうしたプログラミングスタイルの一番のメリットは、Programmer
がLeader
の存在にまったく左右されない点だ。
Leader
がいても、いなくても、いや、昇給を監視しているオブジェクトが何であっても、まったくいなくても、それを気にかける必要がない。
この結果、Programmer
クラスの再利用性は向上する。
次に、Newbie
を考える。
Newbie
は、Programmer
のGetPayRise
の通知を受け取ったら、彼を食事に誘う処理をおこなう。もちろんお祝いをするためだ。
class Newbie
{
private Programmer _Programmer101 = MyCompany.GetProgrammer(101);
void Programmer101_GetPayRise()
{
InviteToDinner(_Programmer101);
}
public Newbie()
{
_Programmer101.GetPayRise += Programmer101_GetPayRise;
}
}
このNewbie
は、_Programmer101
のGetPayRise
変数に自分の処理を「追加」している。
デリゲートには、マルチキャストデリゲートという機能があって、すでに登録されている処理(Leader
のProgrammer101_GetPayRise
)とあわせて、自分の処理(Newbie
のProgrammer101_GetPayRise
)も呼び出すように追加できるのだ。
呼び出し側(Programmer
クラス)は、処理がいくつ登録されているか気にかける必要がない。
そして、追加のためには、+=
演算子を用いる。
そう、「追加」だ。
このNewbie
のメソッドを「追加」するには、その前に何かが登録済みでなければならない。
そうなると、Leader
が_Programmer101
にメソッドを登録してから、Newbie
のメソッドが登録されなければならないことになってしまう。
Newbie
だけ見れば、
if (_Programmer101.GetPayRise == null)
_Programmer101.GetPayRise = Programmer101_GetPayRise;
else
_Programmer101.GetPayRise += Programmer101_GetPayRise;
とすることでこの問題を回避できる。
ところが、今度はLeader
が_Programmer101.GetPayRise = Programmer101_GetPayRise
としているところが問題になる。
これはまったく新しい代入になるので、これ以前にNewbie
のメソッドが登録されていても、それが消去されてしまうのだ。
ここでの本当の問題は、
「Programmer101
に登録されているデリゲートを、外部から初期化できてしまうこと」
であり、こうした抜け穴は塞がなければならない。
抜け穴と言えば、この形では、Leader
やNewbie
が、Programmer
に登録されているデリゲートを呼び出せてしまう点も問題だ。
こうした、デリゲートの変数を公開することによる問題を解決するための機能。
これがイベントの存在理由だ。
イベントによる通知の実現 - Sender
今までのポイントをまとめよう。
- メソッドをデリゲート経由で渡すことによる「通知」の仕組みには、クラス間の依存関係を少なくするメリットがある
- マルチキャストデリゲートという機能により、複数のメソッドをひとつの変数に登録することが可能である
- ところが、デリゲートの変数を直接公開してしまうのには安全性に問題がある
- こうした問題を解決するのがイベントである
ここで、語句を定義する。
- メソッドをデリゲート経由で渡して登録することを「subscribe」
- 渡したメソッドの登録を解除することを「unsubscribe」
- メソッドを受け取り、条件によってその登録されているメソッドを呼び出すオブジェクトを「Sender」
- メソッドを登録し、デリゲート経由で機能を呼び出されるオブジェクトを「Receiver」
いろいろな流儀があるが、この投稿の中では、この語句を使うことにする。
この投稿外で使っても、支障のない語句を選択したつもりだ。
Programmer
の例では、Programmer
クラスがSender役であり、Receiver役はLeader
, Newbie
クラスだ。
さて、イベントを使ったとき、Programmer
の例がどうなるか見てみよう。
まず、Sender役のクラスはどうなるか。
class Programmer
{
public event EventHandler GetPayRise;
private readonly System.Random _Random = new System.Random();
public Programmer(int id) { ... }
public virtual void Work(int quantity)
{
...
if (_Random.Next(1000) == 0)
OnGetPayRise(EventArgs.Empty);
}
protected virtual void OnGetPayRise(EventArgs e)
{
GetPayRise?.Invoke(this, e); // C#6.0以上でのみ可能
}
}
イベントの定義
まず、イベントを定義している。
public event EventHandler GetPayRise;
これは、
- イベントのsubscribe、unsubscribeはどこからでも可能である
- 登録するメソッドは、
EventHandler
と型が一致していなければならない - このイベントの名前は
GetPayRise
である
ということの宣言である。
EventHandler
というデリゲート型は、.NET Frameworkに定義済みの型であり、
- ひとつめの引数が
Object
型、ふたつめの引数がEventArgs
型 - 戻り値はない
という意味になる。
これにより、登録できるメソッドはvoid Xxxxx(object, EventArgs)
という形で宣言されているものに限定される。
デリゲートを呼び出すメソッドの定義
そして、登録されているデリゲートを呼び出すメソッドを定義する。
protected virtual void OnGetPayRise(EventArgs e) { ... }
- メソッドの名前は、
On
+ イベントの名前 - 仮想メソッド
- 可視性は
protected
- 引数は
EventArgs
型
であることに注意して欲しい。
メソッドにこうした名前を付けるのは.NET Frameworkの慣例だ。
可視性と引数については、こうしなければならない理由を後述する。
GetPayRise?.Invoke(this, e);
その内部の処理では、イベントと同名の変数として暗黙的に宣言されているGetPayRise
を使用する。
この変数はデリゲート型の値として扱うことができるので、nullでない場合にのみ、デリゲートを呼び出すようにする。
呼び出す際は、ひとつめの引数にはthis、ふたつめの引数には受け取った引数を渡す。
C#6.0以前には?.
演算子がないので、
protected virtual void OnGetPayRise(EventArgs e)
{
if (GetPayRise != null)
GetPayRise(this, e);
}
という形になる。
スレッド安全性に注意する必要があるのならば、以下のようにする必要があって少々面倒だ。
protected virtual void OnGetPayRise(EventArgs e)
{
var getPayRise = GetPayRise;
if (getPayRise != null)
getPayRise(this, e);
}
イベントを通知したい箇所(この例では、Programmer
のWork
メソッドの内部になる)では、このOnGetPayRise
メソッドを呼び出すようにする。
OnGetPayRise
を呼び出すとき、毎回、new EventArgs()
としてインスタンスを生成してもいいのだが、このクラスにはEmpty
という静的フィールドが用意されており、これをインスタンスの生成の代わりに用いることができる。
毎回インスタンスを生成するよりも、ほんの少しだけ早くなるかもしれない。
Sender側にはsubscribeの記述をしてはいけない
重要なのは、Sender役のクラスにはsubscribeの記述は一切しないことだ。
なんらかの方法でReceiverをSenderに渡し、ここでsubscribeすることもできるのだが、これをしてしまうと前述のメリット、「クラス間の依存関係を少なくする」が完全に失われてしまう。
subscribeの記述はReceiver役のクラスにしよう。
イベントの定義の補足
イベントの可視性とは、あくまで、subscribeとunsubscribeについてだけのものになる。
これがどんな可視性で定義されていようとも、登録されているデリゲートの呼び出し自体は、自分自身か、あるいはその内部クラスからのみ可能であることに注意してほしい。
外部からはもちろん、Programmer
の派生クラスからも、直接、デリゲートを呼び出すことはできない。
また、イベントの名前は動詞や動詞句にしよう。
イベント間で前後関係がある場合は、前のものにingをつけて進行形に、後のものは過去形にする。
例えば、ClosingとClosedのように。
デリゲートを呼び出すメソッドの補足
登録されているデリゲートの呼び出し自体は、自分自身か、あるいはその内部クラスからのみ可能
と述べた。これこそが、OnGetPayRise
というメソッドが必要な理由になる。
Programmer
を継承したSuperProgrammer
クラスを考える。
SuperProgrammer
は技術が高いので、もう少し、昇給の確率を向上させたい。
ところが、SuperProgrammer
からはGetPayRise
変数からデリゲートを呼び出せない。もし、protected
なOnGetPayRise
メソッドがなければ、SuperProgrammer
クラスには、GetPayRise
に登録されているデリゲートを呼び出す方法が一切ないことになる。
OnGetPayRise
がprotected
として宣言されなければならないのは、こうした理由だ。
class SuperProgrammer : Programmer
{
public override void Work(int quantity)
{
...
if (_Random.Next(1000) <= 10)
// Compile Error!
GetPayRise?.Invoke(this, EventArgs.Empty);
}
}
また、EventArgs
型にはまったく情報が含まれていないので、OnGetPayRise
メソッドを呼び出すたびにEventArgs.Empty
を使用する代わりに、OnGetPayRise
から引数を消しさりたくなる衝動にかられるだろう。
// Bad knowhow
protected virtual void OnGetPayRise()
{
GetPayRise?.Invoke(this, EventArgs.Empty);
}
こうした衝動にはあらがわなければならない。
なぜなら、このクラスを継承したSuperProgrammer
では、新たな給料の額を持たせたEventArgs
の派生クラスのオブジェクトを通知に使用したくなるかもしれないからだ。
class SuperProgrammer : Programmer
{
public override void Work(int quantity)
{
...
if (_Random.Next(1000) <= 10)
OnGetPayRise(new SalaryEventArgs(_CurrentSalary + 10000));
}
}
...
class SalaryEventArgs : EventArgs
{
public SalaryEventArgs(int salary) : base() { ... }
}
OnGetPayRise
メソッドに引数がなければ、こうした拡張の芽をつんでしまうことになる。
引数がないメソッドを作りたければ別に定義し、その内部で引数がある方のメソッドを呼び出すようにしよう。
protected virtual void OnGetPayRise()
{
OnGetPayRise(EventArgs.Empty);
}
なお、Sender役のクラスがsealed
として定義されている場合、このクラスを継承することはできないのだから、こうした気遣いは不要になる。
イベントによる通知の実現 - Receiver
Receiver役のクラスには、イベントのsubscribeとunsubscribe、そして呼び出してほしい処理を記述することになる。
Leader
クラスの例で説明しよう。
class Leader
{
private Programmer _Programmer101 = MyCompany.GetProgrammer(101);
private int WorkQuantityForProgrammer101 = 10;
void Programmer101_GetPayRise(object sender, EventArgs e)
{
WorkQuantityForProgrammer101++;
}
public Leader()
{
_Programmer101.GetPayRise += Programmer101_GetPayRise;
}
}
_Programmer101.GetPayRise
はもはやデリゲートの変数ではない。
そのため、Receiver側から登録されているデリゲートを呼び出せてしまうという安全上の問題は解消されている。
デリゲートの変数ではないにもかかわらず、マルチキャストデリゲートにある、複数のメソッドを登録できるというメリットは保ったままなので、いくらでもsubscribeできる。
subscribeは
senderオブジェクト.イベント名 += 登録したいメソッド名;
unsubscribeは
senderオブジェクト.イベント名 -= 登録解除したいメソッド名;
と記述する。
他のメソッドへの参照が登録されているかどうかを気にかける必要はないし、既に登録されているデリゲートの初期化もできないようになっている。
ところで、Programmer101_GetPayRise
のような、イベント用にデリゲートとして渡されるメソッドのことを「イベントハンドラ」と呼ぶ。
イベントの定義時に使用したEventHandler
というデリゲートの型は、まさにこれが理由で名付けられている。
メモリリークに注意する
Receiverの寿命が、Senderとさして変わらず、イベントを常に通知して欲しいのであれば、unsubscribeは不要である。
しかし、Receiverの方が寿命が短い場合はちがう。
Senderは、subscribeしている間、Receiverへの参照を持ちづづける。
そのため、Recevierは不要なったとしても、ガベージコレクタがいつまでたってもReceiverをメモリから削除してくれない。
おまけに、不要なデリゲート呼び出しが積み重なる結果になる。
こうした場合、Receiverが不要になったらきちんとunsubscribeしよう。
今となっては古い情報
イベントのsubscribe, unsubscribeのとき、
_Programmer101.GetPayRise += new EventHandler(Programmer101_GetPayRise);
というように、new EventHandler()
が必要だ、としている情報がある。
しかし、C#3.0からはこれは不要になった。もう付けなくてかまわない。
イベント引数に情報を持たせたイベント通知
プログラマも人間である。人間であるからには休みが欲しい。
そこで、プログラマは労働すると、ある確率で休暇を申請し、それを通知することにしよう。
休暇の申請には、「いつ休むか」の情報が必要だ。
新しいイベント引数型の定義
まず、「いつ休むか」の情報を含んだイベント引数の型を宣言する。
これは、EventArgs
クラスの派生クラスとしなければならない。
class VacationEventArgs : EventArgs
{
public VacationEventArgs(DateTime date)
: base()
{
VacationDate = date;
}
public DateTime VacationDate { get; }
}
EventArgs
を継承したクラスの名前の後ろにはEventArgs
をつけて、イベント引数のための型であることを強調するとよい。
ここでは、休暇情報ということで、VacationEventArgs
クラスとした。
Sender役のクラス
次はプログラマだ。
class Programmer
{
...
public event EventHandler<VacationEventArgs> ApplyVacation;
public virtual void Work(int quantity)
{
...
if (_Random.Next(20) == 0)
OnApplyVacation(new VacationEventArgs(DateTime.Today.AddMonths(1)));
}
protected virtual void OnApplyVacation(VacationEventArgs e)
{
ApplyVacation?.Invoke(this, e); // C#6.0以上でのみ可能
}
}
注意するべき点は、以下の点ぐらいだろうか。
- イベントの定義中、デリゲートの型が
EventHanlder<VacationEventArgs>
になっている - デリゲートを呼び出すメソッドの引数が、
VacationEventArgs
になっている
.NET Frameworkに定義されているデリゲートの型であるEventHandler<TEventArgs>
は、
- ひとつめの引数が
Object
型、ふたつめの引数がTEventArgs
型 - 戻り値はない
という意味になる。
そして、TEventArgs
に与える型は、EventArgs
か、その派生クラスでなければならない。
これが、VacationEventArgs
をEventArgs
を継承して実装した理由だ。
ところで、「変更可能なオブジェクトの参照を漏らさない」という鉄則がある。
これは主にプロパティやメソッドの戻り値として言われがちなルールであるが、イベント引数にもあてはまる金言だ。
class ExVacationEventArgs : VacationEventArgs
{
public ExVacationEventArgs (DateTime date, IEnumerable<DateTime> vacationHistory)
: base(date)
{
VacationHistory = vacationHistory;
}
public IEnumerable<DateTime> VacationHistory { get; }
}
class Programmer
{
public event EventHandler<ExVacationEventArgs> ExApplyVacation;
private List<DateTime> _VacationHistory;
public virtual void Work(int quantity)
{
...
if (_Random.Next(20) == 0)
// Bad!
OnExApplyVacation(new ExVacationEventArgs(DateTime.Today.AddMonths(1), _VacationHistory));
}
}
上記のコードでは、ReceiverからProgrammer
の_VacationHistory
の値を操作することが可能になっている。
ExVacationEventArgs
が保持しているのはIEnumerable<DateTime>
だから直接値を操作することはできないと言えばできないのだが、Receiverがキャストしてしまえば操作し放題になってしまう。
これでは、悪意のあるReceiverから、取得した覚えのない休暇が追加されてしまう恐れがある。
悪い上司から身をまもるために、イベント引数には_VacationHistory
のコピーやReadOnlyのコレクションを渡そう。
public virtual void Work(int quantity)
{
...
var e = new ExVacationEventArgs(
DateTime.Today.AddMonth(1),
_VacationHistory.ToArray());
OnExApplyVacation(e);
}
Receiver役のクラス
さて、Receiver側だ。
Leader
はProgrammer
が休暇を申請すると、スケジュールの調整をしなくてはならない。
class Leader
{
...
void AdjustSchedule(Programmer programmer, DateTime vacation)
{
...
}
void Programmer101_ApplyVacation(object sender, VacationEventArgs e)
{
AdjustSchedule(sender as Programmer, e.Date);
}
public Leader()
{
...
_Programmer101.ApplyVacation += Programmer101_ApplyVacation;
}
}
このイベントハンドラ(Programmer101_ApplyVacataion
)のふたつめの引数がVacationEventArgs
になっている。
イベントが通知されると、つまり、Programmer101_ApplyVacation
が呼び出されると、
- ひとつめの引数には、イベントを発生したオブジェクト、つまりSender。この例では
_Programmer101
- ふたつめの引数には、Senderが生成したイベント引数
が渡される。(もちろん、そうなるようにSenderを実装したからだ)
Programmer101_ApplyVacation
の内部では、この渡された情報を利用して、自分自身のAdjustSchedule
メソッドを呼び出している。
次に、Newbie
に再登場してもらおう。
Newbie
は、Programmer
が休暇を申請すると、何をするわけでもないが、ただうらやましがる。
彼はまだ有給休暇を取得していないので、自分も休むだとか、休んだ上でProgrammer
を遊びに誘うといったことはできないのだ。
class Newbie
{
...
void Programmer101_ApplyVacation(object sender, EventArgs e)
{
Envy();
}
public Newbie()
{
...
_Programmer101.ApplyVacation += Programmer101_ApplyVacation;
}
}
Programmer101_ApplyVacation
のふたつめの引数の型がEventArgs
になっているところに注意して欲しい。
実のところ、このイベントハンドラのふたつめの引数の型はVacationEventArgs
かその基底クラスでさえあれば、どんな型でもかまわない。Object
にしてもよいのだ。
Newbie
の場合、うらやましがるのにはなんの情報も必要ないため、EventArgs
として通知を受け取っている。
今となっては古い情報
情報を渡すイベントを定義する場合は、
delegate void MyEventHandler(object sender, VacationEventArgs e);
というように、デリゲートを定義しよう、と書かれている情報がある。
C#2.0以降では、EventHandler<TEventArgs>
がすでに定義されているので、こうした定義は不要になった。
まとめ
- イベントは、デリゲートによるコールバックメカニズムに、安全なsubscribe/unsubscribe機能を追加する
- イベントを用いると、クラス間の依存関係を少なくすることができる
- デリゲートの型は、自分で定義するよりも定義済みのものを選択しよう
- デリゲートを呼び出すメソッドを定義するときは、そのクラスを継承する場合について考慮しよう
- Sender役のクラスには、subscribeの記述をしてはいけない
- Receiverの寿命がSenderよりも短い場合、unsubscribeしてメモリリークを防ごう
- 情報を渡すイベントのために、
EventArgs
を継承したクラスを定義し、イベントのデリゲートの型にEventHandler<TEventArgs>
を使用しよう -
EventArgs
の派生クラスのインスタンス作成の際、変更可能なオブジェクトの参照を漏らさないようにしよう
後書き
以前、イベントについてまとめた投稿について、
「混乱があるように見えるのだが、(何かの目的があって)わざとやっているのではないか」
というコメントをして、いたく投稿者の気分を害してしまったことがあった。
http://qiita.com/hibara/items/9fd56a5d594c000a5df0
罪滅ぼしというわけでもないのだが、これでイベントについてスムーズな理解をしてもらえたらと思う。
参考情報
C#6.0時代のイベント実装方法
http://qiita.com/zakuro9715/items/107b9aa60b114beb206a
++C++; // 未確認飛行C
http://ufcpp.net/