LoginSignup
8
12

More than 1 year has passed since last update.

とても雑にC#のイベント処理 超入門&再入門

Last updated at Posted at 2023-01-29

イベントとデリゲートの違いとかが全然分からんままだったのでメモ。

▼ デリゲートについて分かっていることが前提なので前編(?)も是非。

:pushpin: 幼児でも分かるC#のイベント処理 概要

とても雑にイベントとデリゲートの違い

デリゲートは C#の型 の一種だよ。
C#の型には、組み込み型、配列、クラス型、構造体型、インタフェース型、列挙型、そして デリゲート型 などがあるよ。

イベントは C#のメンバ の一種だよ。
C#のメンバは、プロパティ、メソッド、フィールド、定数、コンストラクタ、デストラクタ、インデクサ、演算子、そしてイベント だよ。

とても雑にイベントとデリゲートはどんな関係?

イベントは、プロパティと似たようなメンバで、(publicで)宣言した変数を他のクラスから使えるようになるよ。

プロパティと第一に違うのは、イベントではデリゲート型の変数しか宣言できないことだよ。つまりイベントはデリゲート専用に作られたメンバなんだ。

デリゲートさんは普段どんな舞台でも大活躍のカメレオン俳優。

C#のイベント駆動型プログラムでは、デリゲートさんは「イベント通知役」を演じることになっているよ。デリゲートさんにこの役を完璧に演じてもらうために「イベント」という専用の衣装が用意されているんだ。この衣装を着ると、イベント通知役にふさわしくない身動きが一切とれなくなって、完全に役に入り込めるようになっているんだ。

とても雑に イベントのデリゲート と プロパティのデリゲート の違い
デリゲートをイベントとして宣言すると、他のクラスから使えようになるよ。
でも、デリゲートをプロパティとして宣言しても他のクラスから使えるよね。

何が違うの? それはね
イベントには プロパティにはない制限をかけるパワー!がある ところだよ。

どうして制限があるの? それはね
ボタンをポチポチしたら動くようなGUIアプリなどの "イベント駆動型プログラム" ってやつを どんだけオバカさんがコーディングしても 絶対に安全に作れるようにするためだよ。

:pushpin: 基本用語のざっくり意味

用語の意味から不安な人はOPEN♪

イベント駆動型プログラム

  • 普段は動いてないように見えて、指示があるたび動くタイプのプログラムをイベント駆動型プログラムという。
  • 代表的なのが、ユーザーがボタンをポチッと押したら処理が実行される「GUIアプリ」。
  • 人間からの指示だけでなく、別のアプリ(プロセス)やOSやからの指示を受け取るプログラムもある。

イベント

  • イベント駆動型プログラムでは、処理が走るきっかけになる出来事の概念のことを「イベント」という。「ボタンがクリックされる」「コンソールに文字が入力される」などのユーザーがアプリに対して行う操作や、別プロセスやOSなどで起こる出来事を含む。

  • C#には 「イベント」という種類のメンバがある。
    デリゲートをイベント駆動型プログラミングにピッタリの形にして使う仕組み。
    「ボタンが押されたよ!動け!」って感じで、イベントの発生を通知する存在。
    メンバの一種ということはお馴染みの フィールド、プロパティ、メソッド、コンストラクタ たちの仲間。

イベントハンドラ

  • イベント駆動型プログラムでは、イベントが発生した時に実行される処理の概念を「イベントハンドラ」という。

  • C#のコードレベルではこの2つ。

    • EventHandler系デリゲートに登録するメソッド
      イベントが発生したときに実行される処理そのもの。
    • EventHandler系デリゲート (を含めて言う場合もある)
      イベントが発生したときに処理を呼び出す仲介者。
EventHandler型デリゲートの定義
public delegate void EventHandler(object sender, EventArgs e);
  • .NETではデリゲートを使ってイベントハンドラを実現している。
  • 標準ライブラリにはイベント発生時に呼び出すためのデリゲートが定義されている。
    • EventHandler型デリゲート
    • xxxEventHandler型デリゲート
  • 自分で定義したデリゲートも使えるが、MS的にはEventHandler型を使うのがオススメ。

:pushpin: 「イベントとデリゲートの違い」への誤解

赤ん坊プログラマーがものすごい勘違いしてたことを書いときます。
まじで次に生まれてくる赤ん坊プログラマーたちの役に立ってほしいです。

ネット検索すると「C#のイベントとデリゲートの違い」みたいなタイトルの記事がそこそこ存在している。

そういう記事によくある説明

  • イベントはデリゲートと同じように使えます
  • デリゲートの機能はイベントの機能と似ているが、使われ方が違います

そしてこれを読んだ赤ん坊プログラマー筆者
「わかったぞ!イベントとデリゲートの2種類があって、どっちか1つ選べるってことか!」

はいこれ間違ってます・・・工エエェェ(´д`)ェェエエ工

実際には、イベントとデリゲートはこのような対比関係ではない。

なぜなら イベントはメンバであり、デリゲートは型だからだ。
なのでイベントとデリゲートを比べることは、例えば、「プロパティとクラスの違いって何だろう?」と考えるのと同じだ。たぶん比べようと思ったことはないだろう。

:pushpin: イベント vs プロパティ

とても雑にいうと、皆さん知ってのとおりだが、こういう雑な説明があるとする。

クラスとプロパティの関係
クラス型の変数を、クラス内に「プロパティ」として宣言すると、他のクラスから使えるようになる。

ええ、知ってますともね。
ちなみにクラス型だけじゃなくて、

プロパティで宣言できる変数の型
- 組み込み型
- クラス型
- インタフェース型
- 構造体型
- デリゲート型
- etc...

あまねく型が宣言できる。

そして、これと同じノリで

デリゲートとイベントの関係
デリゲート型の変数を、クラス内に 「イベント」として宣言すると、他のクラスから使えるようになる。
※ただしプロパティみたいに完全に自由に使えるわけではないので注意!

っていう説明になるのだ。
ただし宣言できるのは

イベントで宣言できる変数の型
- デリゲート型
以上

デリゲート専用に作られたなんて、贅沢なメンバだね( ^ω^)・・・。

デリゲートとイベントの関係性はこんなもんだ。

むしろイベントとプロパティの違いがもっと知りたくなってきたよね。
「完全に自由に使えるわけではない」って意味深すぎない・・・?!wktk

じゃあ何が違うの・・・?ということで
イベントとして宣言されたデリゲートプロパティとして宣言されたデリゲートを比較することでイベントの理解にレッツゴー。

1. :trident: 概要比較

  • プロパティ

    • メンバの一種。
    • 主に変数を他クラスから使うためのもの。
    • デリゲートもデリゲート以外も宣言できる。
    • get/setアクセサを書ける。
  • イベント

    • メンバの一種。
    • 主に変数を他クラスから使うためのもの。
    • 宣言できるのはデリゲートだけ。
    • add/removeアクセサを書ける。
    • 宣言したデリゲートについて独自の制限を加える。

2. :trident: 宣言

(アクセス修飾子) event デリゲートの型 変数名

例として、System.Windows.Forms.Buttonクラス。

System.Windows.Forms.Buttonクラス
public event EventHandler Click; // イベント宣言
  • eventキーワード をつけて宣言する
  • デリゲート型以外の型にeventキーワードをつけることはできない。
    (イベントはデリゲート型じゃないとダメだよって怒られる)

※ 宣言方法的にはプロパティよりもフィールドに近い。 実はMS公式ではイベントをフィールドと比較している。ただ、フィールドはpublicが推奨されないので、目的の近いプロパティと比較したほうが分かりやすいと勝手に思っている。未確認飛行Cさんではプロパティと比較している。

3. :trident: メソッドの登録/解除

プロパティでもイベントでも、
イベントを宣言したクラスの外から

  • +=演算子でメソッドを登録できる。
  • -=演算子でメソッドを登録解除できる。
Buttonクラスの外から
Button button = new Button();

button.Click += Callback1; // 簡略化した書き方で登録
button1.Click += new EventHandler(Callback2); // お堅い書き方で登録
button.Click -= Callback2; // メソッドを登録解除

4. :trident: 代入

宣言クラスの外から

  • プロパティには代入ができる。
  • イベントには代入ができない
Buttonクラスの外から
button.Click = Callback1; // コンパイルエラー ("+="か"-="の左にしか書けないよ!と怒られる)

5. :trident: デリゲート経由でメソッドを実行

プロパティと同じなら、デリゲート変数に引数を渡すと登録したメソッドを実行してくれる。はずだが…?

Buttonクラスの外から
button.Click(button, EventArgs.Empty);
// コンパイルエラー ("+="か"-="の左にしか書けないよ!と怒られる)
  • イベントを実行できるのはそのイベントを宣言したクラスだけに制限される。

6. :trident: アクセサ

イベントにもアクセサがある。

  • アクセサ比較

    • プロパティアクセサ getアクセサ/setアクセサ
    • イベントアクセサ addアクセサ/removeアクセサ
  • イベントアクセサ

    • addアクセサ メソッドを登録するときに実行する処理を書く。
    • removeアクセサ メソッドを登録解除するときに実行する処理を書く。
    • アクセサは省略可。(プロパティアクセサ同様)
    • 省略した場合はコンパイラが自動生成してくれる。(プロパティアクセサ同様)

▼コンパイラが自動生成するイベントアクセサ。

自動生成前
public event EventHandler X;
自動生成後(あくまでイメージで実際はもっと複雑)
private EventHandler _X;

public event EventHandler X
{
    add { _X += value; }
    remove { _X -= value; }
}

7. :trident: 【まとめ】イベントで できること・できないこと

デリゲートをイベントとして宣言すると、宣言クラス外からの使い方に制限がかかる

  • 宣言クラスの外から

    • メソッドの登録/解除 
    • デリゲート変数への代入 ×
    • デリゲート実行 ×
  • 宣言クラスの中から

    • 何でも 

:pushpin: 補足的まとめ

イベントでできることは全部プロパティでもできるから、代わりにプロパティを使うこともできるっちゃできるけど…
イベントの代わりにプロパティを使うと、イベント駆動型プログラムの設計的にはできちゃダメなことまで何でもできてしまうからバグの温床になりよろしくない。ということである。

.NETガイドラインでは、Publisher/Subscriberパターンと思しきものに則した実装がオススメされており、それがイベントを使った方法だ。

イベントを使えば、どんなにオバカさんや、どんなに疲れたブラック勤めプログラマでも、絶対にバグらずイベント処理を実装できると保証されるのだ!
やってはいけないことが禁止されるのだから間違いようがないze。

:pushpin: 【おまけ1】イベント駆動型プログラム

ただのメモ

イベント駆動型プログラムの大まかな構成

  • イベントループ イベントの発生を待ち受けるためのループを持つ機構。
  • イベントハンドラ イベント発生時の処理を行う機構。

イベントが発生するたびにイベントハンドラを呼び出す方法

イベントループの実装例で分かる。
未確認飛行++C++さんのこちらの記事でサンプルコードを動かして体験可能。

イベントループでは [コンソール入力待ち→入力されたらイベントハンドラ(デリゲート)を呼び出す] という動きを半永久的に繰り返している。
このイベントループの実装はどんな場合も同じような実装になるとのこと。

:pushpin: 【おまけ2】イベントハンドラの引数(object sender, EventArgs e)って何者?

ただのメモ

(object sender, EventArgs e)
いつも引数で受け取ってるけど使わずに無視してるこいつら。
一体何者?どうやって使うの?をざっくりメモ。

第1引数 :trident: object sender

イベントが発生したオブジェクトが入っている。
button1がクリックされて呼ばれたメソッドにはbutton1が渡されている。

1つのメソッドは複数のイベントに紐付けることができるので、メソッドを共通化する場合に、例えば呼び出し元がbutton1なのかbutton2なのか?で処理を分岐させたりするなどの使い道があるようだ。

第2引数 :trident: EventArgs e

発生したイベントの情報が入っている。
具体的にどんな情報?というと、「千差万別」。。

EventArgsクラスは基本的に基底クラスとして存在していて、実際はEventArgsの派生クラスたちが思い思いの情報を送りつけてくる。どんな情報を送ってくるかは各クラスの定義を確認するのみ。
その情報を使った処理をしたいときに引数を使えばよろしそうだね。

ちなみに何も情報がないときは、基底EventArgs型のEventArgs.Empty値が送られてくる。

8
12
0

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
8
12