DelegateとActionとEvent(C# WPF)

C#

DelegateとかEventとか、Event handlerとか、Actionとか、
似てるようで微妙に違って理解に時間が掛かりますよね。
コアな概念はDelegateで、これに制約を掛けていたり実装のされ方の違いで名称が分かれます。
なのでまずはDelegateを理解しましょう。

Delegate

デリゲートとは直訳すると委任・委譲という意味です。
と言われてもよく分からないですよね。

C#的には複数のメソッドを格納できる箱です。
List<T>のようなTの部分がobjectではなく、メソッドになったようなものと思ってください。

delegateの宣言は以下のように行います。

delegate void Hogehoge(int i);

上記の例の場合、Hogehogeというintを引数に取り、voidを返すdelegateを宣言したことになります。delegateは引数と返り値を指定する必要があるということですね。逆に言えば、それ以外は指定しませんので、引数と返り値が一致するメソッドであれば何でも格納できます。

delegateを使うには以下のように利用します。

delegate void Hogehoge(int i);

public Class Test
{
    public Hogehoge fuga;
    //初期化の方法
    fuga = delegate(int i) { };
    fuga = delegate { };
    fuga = (int i) => { };
    fuga = (i) => { };
    fuga = i => { };
}

fugaを宣言しただけだとNullなので if( null != fuga)  なんてコードを書く必要が出てきますが、初期化で何もしないメソッドを入れておくことでNull チェックを省けます。
匿名メソッドはパラメータの記述を省けるため引数があってもdelegate{}で通ります。
同じくラムダ式は引数が1つであれば()を省けるため i =>という書き方ができます。

ここでラムダ式とdelegateの違いってなんなの?って疑問が湧くと思います。
似てるようで違うってのは難しいですよね。
簡単にいうとラムダ式はdelegateにも成りうるしExpressionにも成りうるため、同じものとして扱える場合と扱えない場合があります。

実際にdelegateを使う例を見てみましょう。

delegate void Hogehoge();

public Class Test
{
    public Hogehoge fuga = delegate { };
    fuga += fugafuga
    fuga();

    private fugafuga(){
        Console.WriteLine("FUGA")
    }
}

fugaという名前のデリゲートを作成し、何もしないデリゲートで初期化しています。
そしてfugafugaメソッドを+=でfugaに追加し、fuga()でデリゲートの中身を実行しています。
fugaがfugafugaの処理を行っているようにも見えるので、委譲という意味なのでしょうね。

この例ではメソッドを1つしか格納していない(正確には何もしないメソッドと合わせて2つ)ので、あまりメリットを感じないと思います。C#のdelegateの特徴として、メソッドを複数格納でき、まとめて処理を発火できるようになり利便性が急激に増してきます。この特徴から後述のEventとして多用されるわけです。

Action, Func等々

ざっくりとDelegateの使い方を見てきました。
delegateは非常に便利ため多用します。
多用するということは、何度もdelegateの宣言が必要になります。
そうすると大量のdelegateが発生していしまうため、C#は標準でいくつかのDelegateが定義されています。代表的なものが以下になります。

  • Action     ・・・返り値がvoid
  • MethodInvoker ・・・返り値がvoid
  • Func      ・・・返り値あり
  • Predicate   ・・・返り値がbool

ActionとMethidoInvokerはほぼ同義です。
上で見た例のようにdelegate void fuga(int i)のようなデリゲートを宣言せずとも、
Action<int> fuga = i => { }
として使えます。

Actionの場合は返り値がvoidです。
引数を指定したい場合はAction<>の<>内に引数を順に格納します。

Action<int, int> fuga = delegate{};
Action<int, int> fuga = (int i, int j) => {};

Funcは返り値を<>内の最後に指定できます。
引数はActionと同様に指定します。

Func<int, string> fuga = delegate(i){ return i.ToString(); };
Func<int, string> fuga = (int i) => { return i.ToString(); };
Func<int, string> fuga = i => i.ToString();

上記の場合は引数intで、返り値がstringです。

ということでActionやFuncは既定義のdelegateです。
単なるdelegateです。

Event, EventHandler

じゃあEventってなんなのさ?
端的にいうと制約を課されたdelegateです。中身はdelegateです。
eventという文字を付けると特殊な制約を課せます。

その制約は、
・クラス内でしか定義できない
・実行はクラス内のメソッドからしか行えない
の2点です。

delegate void Hoge();
public Class Test{
    public event Hoge Fuga = delegate{};
    public fugafuga(){
        Fuga();
    }
}

1つ目の制約ですが、delegateのようにClassの外には定義できません。
なぜかというとEventの意味通り、クラス内のEventを捉えるために使うからです。
クラスの外部にEventがあると何のEventなのか分からないですよね。

そしてもう一つの制約がクラス内からしか実行できないことです。
これもEventのための制約ですね。
せっかくdelegateにメソッドを格納しても、どこからでも実行できてはEventとして制御できないです。よそから意図しないタイミングで呼ばれる可能性を排除しています。private関数のような感じですが、delegateにはメソッドを登録してもらう必要があるので全体としてはpublic。だが、発火のみはprivateな感じです。publicだけどprivateなので恐らくeventを発案した人も苦労したんでしょうね。public eventという表記に苦悩を感じます。

EventHandler

EventHandlerは、これもdelegateです。
引数にobjectとEventArgs型を持ち、voidを返すdelegateです。

名前がEventHandlerなのでevent属性持ちかと思いきや、単なるdelegateです。
ジャンルとしてはActionやFuncと同等です。
ただし作られた意図としてはsenderとEventArgsを使ってイベント駆動させたい時に使うものです。それ以外の場合にdelegateとして使うと誤解を招くのでやめましょう。

C#
スポンサーリンク
Once and Only

コメント