154
197

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[C#] イベント入門

Last updated at Posted at 2017-01-18

本稿について

この投稿は、C#についてあまり経験のない方がイベントの使い方について理解することを目的としている。
説明の範囲は、

  • イベントを使用したプログラミングのメリットを理解する
  • イベントを使用したクラスを実現できる
  • その際、安全性や拡張性に留意できる

ようになることにある。以下については、説明の対象外になる。

  • イベントのアクセサを自分で定義する
  • イベントハンドラとしてラムダ式を使用する

また、前提として、

  • ジェネリックプログラミング

の知識があるものとしている。

イベントとは

C#におけるイベントとは、一言で言えば、
「デリゲートによるコールバックメカニズムに、安全な購読と購読解除機能を追加するための文法」
ということになるだろう。
そのため、イベントを理解するには、まず、デリゲートやコールバックについておさえておく必要がある。

コールバックとデリゲート

ある処理に対し、処理そのものを渡したいことがある。

int型の配列を持っているオブジェクトがあるとしよう。

hoge.cs
class Hoge
{
    private readonly IList<int> _list = new List<int>();
}

この_listの中には、何かの要素が含まれている。
そして、この_list内の要素のうち、奇数の要素を取り出す処理を考える。

hoge.cs
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>型の引数を使う。

hoge.cs
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は昇給するとその情報をうっかり外へ通知する。
通常は、昇給イベントを用意するのだが、今回はデリゲートだけを使用して、昇給を通知することを考えよう。

Programmer.cs
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に登録されているデリゲートを呼び出す仕組みができた。

では、昇給が通知されたときに呼び出される処理を追加してみよう。

Leader.cs
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は、昇給を通知されると、彼への仕事量を増やす処理をおこなう。
コンストラクタの中で_Programmer101GetPayRise変数に自分の処理(Programmer101_GetPayRise)を登録している。
これで、Programmer101Workを呼び出せば、ごく稀に昇給が通知され、彼への仕事量を増やすことができるようになった。

こうしたプログラミングスタイルの一番のメリットは、ProgrammerLeaderの存在にまったく左右されない点だ。
Leaderがいても、いなくても、いや、昇給を監視しているオブジェクトが何であっても、まったくいなくても、それを気にかける必要がない。
この結果、Programmerクラスの再利用性は向上する。

次に、Newbieを考える。
Newbieは、ProgrammerGetPayRiseの通知を受け取ったら、彼を食事に誘う処理をおこなう。もちろんお祝いをするためだ。

Newbie.cs
class Newbie
{
    private Programmer _Programmer101 = MyCompany.GetProgrammer(101);

    void Programmer101_GetPayRise()
    {
        InviteToDinner(_Programmer101);
    }

    public Newbie()
    {
        _Programmer101.GetPayRise += Programmer101_GetPayRise;
    }
}

このNewbieは、_Programmer101GetPayRise変数に自分の処理を「追加」している。
デリゲートには、マルチキャストデリゲートという機能があって、すでに登録されている処理(LeaderProgrammer101_GetPayRise)とあわせて、自分の処理(NewbieProgrammer101_GetPayRise)も呼び出すように追加できるのだ。
呼び出し側(Programmerクラス)は、処理がいくつ登録されているか気にかける必要がない。
そして、追加のためには、+=演算子を用いる。

そう、「追加」だ。
このNewbieのメソッドを「追加」するには、その前に何かが登録済みでなければならない。
そうなると、Leader_Programmer101にメソッドを登録してから、Newbieのメソッドが登録されなければならないことになってしまう。

Newbieだけ見れば、

Newbie.cs
if (_Programmer101.GetPayRise == null)
    _Programmer101.GetPayRise = Programmer101_GetPayRise;
else
    _Programmer101.GetPayRise += Programmer101_GetPayRise;

とすることでこの問題を回避できる。
ところが、今度はLeader_Programmer101.GetPayRise = Programmer101_GetPayRiseとしているところが問題になる。
これはまったく新しい代入になるので、これ以前にNewbieのメソッドが登録されていても、それが消去されてしまうのだ。

ここでの本当の問題は、
Programmer101に登録されているデリゲートを、外部から初期化できてしまうこと」
であり、こうした抜け穴は塞がなければならない。
抜け穴と言えば、この形では、LeaderNewbieが、Programmerに登録されているデリゲートを呼び出せてしまう点も問題だ。

こうした、デリゲートの変数を公開することによる問題を解決するための機能。
これがイベントの存在理由だ。

イベントによる通知の実現 - Sender

今までのポイントをまとめよう。

  • メソッドをデリゲート経由で渡すことによる「通知」の仕組みには、クラス間の依存関係を少なくするメリットがある
  • マルチキャストデリゲートという機能により、複数のメソッドをひとつの変数に登録することが可能である
  • ところが、デリゲートの変数を直接公開してしまうのには安全性に問題がある
  • こうした問題を解決するのがイベントである

ここで、語句を定義する。

  • メソッドをデリゲート経由で渡して登録することを「subscribe」
  • 渡したメソッドの登録を解除することを「unsubscribe」
  • メソッドを受け取り、条件によってその登録されているメソッドを呼び出すオブジェクトを「Sender」
  • メソッドを登録し、デリゲート経由で機能を呼び出されるオブジェクトを「Receiver」

いろいろな流儀があるが、この投稿の中では、この語句を使うことにする。
この投稿外で使っても、支障のない語句を選択したつもりだ。

Programmerの例では、ProgrammerクラスがSender役であり、Receiver役はLeader, Newbieクラスだ。

さて、イベントを使ったとき、Programmerの例がどうなるか見てみよう。
まず、Sender役のクラスはどうなるか。

Programmer.cs
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);
}

イベントを通知したい箇所(この例では、ProgrammerWorkメソッドの内部になる)では、この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変数からデリゲートを呼び出せない。もし、protectedOnGetPayRiseメソッドがなければ、SuperProgrammerクラスには、GetPayRiseに登録されているデリゲートを呼び出す方法が一切ないことになる。
OnGetPayRiseprotectedとして宣言されなければならないのは、こうした理由だ。

SuperProgrammer.cs
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から引数を消しさりたくなる衝動にかられるだろう。

NG.cs
// Bad knowhow
protected virtual void OnGetPayRise()
{
    GetPayRise?.Invoke(this, EventArgs.Empty);
}

こうした衝動にはあらがわなければならない。
なぜなら、このクラスを継承したSuperProgrammerでは、新たな給料の額を持たせたEventArgsの派生クラスのオブジェクトを通知に使用したくなるかもしれないからだ。

SuperProgrammer.cs
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メソッドに引数がなければ、こうした拡張の芽をつんでしまうことになる。
引数がないメソッドを作りたければ別に定義し、その内部で引数がある方のメソッドを呼び出すようにしよう。

OK.cs
protected virtual void OnGetPayRise()
{
    OnGetPayRise(EventArgs.Empty);
}

なお、Sender役のクラスがsealedとして定義されている場合、このクラスを継承することはできないのだから、こうした気遣いは不要になる。

イベントによる通知の実現 - Receiver

Receiver役のクラスには、イベントのsubscribeとunsubscribe、そして呼び出してほしい処理を記述することになる。
Leaderクラスの例で説明しよう。

Leader.cs
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クラスの派生クラスとしなければならない。

VacationEventArgs.cs
class VacationEventArgs : EventArgs
{
    public VacationEventArgs(DateTime date)
        : base()
    {
        VacationDate = date;
    }

    public DateTime VacationDate { get; }
}

EventArgsを継承したクラスの名前の後ろにはEventArgsをつけて、イベント引数のための型であることを強調するとよい。
ここでは、休暇情報ということで、VacationEventArgsクラスとした。

Sender役のクラス

次はプログラマだ。

Programmer.cs
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以上でのみ可能
    }
}

注意するべき点は、以下の点ぐらいだろうか。

  1. イベントの定義中、デリゲートの型がEventHanlder<VacationEventArgs>になっている
  2. デリゲートを呼び出すメソッドの引数が、VacationEventArgsになっている

.NET Frameworkに定義されているデリゲートの型であるEventHandler<TEventArgs>は、

  • ひとつめの引数がObject型、ふたつめの引数がTEventArgs
  • 戻り値はない

という意味になる。
そして、TEventArgsに与える型は、EventArgsか、その派生クラスでなければならない。
これが、VacationEventArgsEventArgsを継承して実装した理由だ。

ところで、「変更可能なオブジェクトの参照を漏らさない」という鉄則がある。
これは主にプロパティやメソッドの戻り値として言われがちなルールであるが、イベント引数にもあてはまる金言だ。

NG.cs
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のコレクションを渡そう。

OK.cs
public virtual void Work(int quantity)
{
    ...
    var e = new ExVacationEventArgs(
        DateTime.Today.AddMonth(1),
        _VacationHistory.ToArray());
    OnExApplyVacation(e);
}

Receiver役のクラス

さて、Receiver側だ。
LeaderProgrammerが休暇を申請すると、スケジュールの調整をしなくてはならない。

Leader.cs
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を遊びに誘うといったことはできないのだ。

Newbie.cs
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/

154
197
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
154
197

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?