Help us understand the problem. What is going on with this article?

なんとなくで来ちゃった人向けのデリゲートとイベントハンドラ再復習

More than 1 year has passed since last update.

この記事の目的

今までなんとな~くでやれてきちゃったけど、とうとう逃げられなくなって明日までにデリゲートとイベントハンドラ使えるようにしなきゃいかん!という人向けの、復習記事です。
こいつらのポイントを理解して、
①何故コードが動かないのか
②どうしたらコードが動くようになるのか
上記二点の仮説を立てられる位になることを目指します。

対象読者

・今までイベントハンドラやる機会なかったけど、明日やらなきゃいけなくなった
・イベント周りの処理よくわかってないけど動くからいいや~って気持ちでいたら、とうとう何やっても動かなくなった
・全部気合で乗り切ってきたけど、気合だけでは乗り切れなくなった
そんなあなたのための記事です。

復習① デリゲートってなんだっけ?

delegate:委譲、譲渡
用語の意味なんかどうでもいいですね。
デリゲートは、メソッドをオブジェクトの一種として使うことができる機能です。

デリゲートの書き方
 修飾子 delegate 戻り値の型 好きな名前(引数の型 引数);
 ex : private delegate void Sum(int a, int b);

この時、同じ型と引数を持つメソッドをいくつでも、このデリゲートの中に入れ込むことができます。

デリゲート例文
namespace ConsoleApplication
{
    /// デリゲートを定義します。
    public delegate void sumDelegate(int a, int b);

    class Program
    {
        static void Main(string[] args)
        {
            // メソッドをデリゲートに代入
            sumDelegate sumDele = Sum;
            // 引数を入れて、Sumメソッドを使う
            sumDele(1, 2);
        }

        /// 引数を足し合わせて、コンソールに表示します。
        static void Sum(int a, int b)
        {
            int sum = a + b;
            Console.WriteLine(sum.ToString());
        }
    }
}

コンソールに表示される文字列 3

よくあるC#基礎の本「これでデリゲートが使えるぞ!よくわかったか?」
私「わからねえ」
よくあるC#基礎の本「はい」

デリゲートがなんでよくわからないのか?

これは持論なんですけど、本で学習するだけでは、使い所がピンとこないからだと思ってます。
と、いうわけで今回はデリゲートの使いどころとして2つあげました。
①データ処理の汎用化
②イベント発生時に特定の処理を行うため、イベントハンドラに渡す

①の場合は、ネックになるのはデリゲートだけだと思うので、多分気合で何とかなります。※ならなかったらすいません
問題は②です。こっちはデリゲートに加えてイベント処理も入ってくるのでもう何が何やら。
それこそ、何がわからないのかもわからない状態。
のでここではデリゲートは同じ戻り値の型/同じ引数の型を持つメソッドを代入できるという所だけ押さえて、次のイベントハンドラに移りましょう!

復習② イベントハンドラってなんだっけ?

event handler:イベントを取り扱うもの
私は言葉から入りたい人なのでスルーしてください。
イベントハンドラとは、イベントが発生した時に行う処理のことです。
イベントハンドラに処理を渡すときは、先に出てきたデリゲートを渡してやります。
するとそのイベントが発生したときに、デリゲートで渡した処理が勝手に走ってくれます。すご~い!

そんなこと言われてもイメージつかない

①今すぐVisual Studio立ち上げてWindowsフォームアプリケーションを新規作成してください
②Form1の適当なところをダブルクリックしてください
③自動生成されたメソッド右クリックしてすべての参照を確認してください
④今のcsファイルじゃない方(Designer.cs)に行ってください
はい!そこにあるのがイベントハンドラです!おめでとう!

なんでイベントハンドラにデリゲート渡すと動くの?

なんででしょうね~、というわけで、イベントハンドラの定義を見てみましょう。

割とオーソドックスなイベントハンドラの定義
 delegate void EventHandler(object snder, EventAgrs e);

そう!イベントハンドラの本質はデリゲートなんですね!
先に出てきたポイントの通り、デリゲートは同じ戻り値の型/同じ引数の型を持つメソッドを代入できるので、イベントハンドラと同様の型のメソッド作って、それを代入すればイベント発生時にその処理を実行してくれるというわけです。

イベントハンドラ例文
namespace WindowsFormsApplication
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            // フォームクローズイベントに、Form1_FormClosedメソッドの処理を追加する
            FormClosed += new FormClosedEventHandler(Form1_FormClosed);
        }

        /// <summary>
        /// フォームが閉じたら確認のダイアログを表示します。
        /// </summary>
        /// <param name="sender">なんだこれ?</param>
        /// <param name="e">誰だおまえは??</param>
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            MessageBox.Show("閉じるよ~");
        }
    }
}

よくわかったかな?わかったら天才です!
明日は上司に100回褒められることでしょう。おめでとう!
ぶっちゃけWindows Formを軽くいじるくらいなら、これくらいの知識で十分だと思います。
例えば、10個のボタンを押したときに、似たような処理をさせたいとか。
電卓アプリ作成なんかにはありがちですね。
でも違うんだ!これは仕事なんだ!電卓作成なんてそんな生ぬるいもんじゃねえんだよ!という方。握手。
というわけで、そんな上っ面じゃどうしようもない人向けにもう少し詳しく見ていきましょう。

イベント処理やろうとした時に一度は疑問に思うやつ「自動生成されるコードにある引数、何?」

正直いまだにはっきりわかってません。助けてくれ。

メソッド引数の謎
private void Form1_FormClosed(object sender, FormClosedEventArgs e) { }

このobject senderEventArgs eです。

object sender

みんな大好きMSDNを見てみると「イベントのソース」と出てきます。はぁ。
更に読み進めると「イベントを発生させるインスタンスを参照します」とも出てきます。はぁ。
色々なブログを読み漁ると、どうやらイベントの発生元のコントロールオブジェクトが格納されているようです。

例えば、ボタンクリックイベントが発生した場合、発生元のボタンコントロールが入ります。
フォームクローズイベントが発生した場合、発生元のフォームコントロールが入ります。
そんな感じ。

EventArgs e

ここでもやっぱりMSDN。「イベント データを格納している」「型 EventArgs から派生し、イベント データを保持します」はぁ…。
またもや検索しまくると、どうやらこっちは補足情報のようで…。うーん。

よくわからないので自分でも確認してみました。

デリゲートの引数確認
        /// フォームが閉じたら確認のダイアログを表示します。
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
       // 1行目:1つ目の引数確認
            // 2行目:2つ目の引数確認
            // 3行目:ついでにイベント発生原因を格納したプロパティ確認
            MessageBox.Show(sender.ToString() + "\n" + e.ToString() + "\n" + e.CloseReason.ToString());
        }

結果
event.png
一つ目の引数に、イベントの発生元コントロールが入るのは本当のようです。
二つ目の引数はFormClosedEventArgsですね。
発生したイベントの情報が入るというのは、発生したイベントが、EventArgsクラスを継承したクラスのインスタンスが入ってくるようです。
EventArgsクラス:https://msdn.microsoft.com/ja-jp/library/system.eventargs(v=vs.90).aspx

つまり、コード上でイベントを無理くり発生させたいなら、上記のものたちを引数として渡せば、イベント発生させられるのでは…?

コード上でメソッド呼び出して、イベント発生時の処理を呼び出す実験

仮説がたったなら、実際にやってみましょう。

実験
namespace WindowsFormsApplication3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            // フォームクローズイベントに、Form1_FormClosedメソッドの処理を追加する
            FormClosed += new FormClosedEventHandler(Form1_FormClosed);
        }

        /// <summary>
        /// フォームが閉じたら確認のダイアログを表示します。
        /// </summary>
        /// <param name="sender">イベントの発生元になったコントロール</param>
        /// <param name="e">イベント情報</param>
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            MessageBox.Show(sender.ToString() + "\n" + e.ToString() + "\n" + e.CloseReason.ToString());
        }

        /// <summary>
        /// Form1_FormClosedメソッドを呼び出します。
        /// </summary>
        /// <param name="sender">イベントの発生元になったコントロール</param>
        /// <param name="e">イベント情報</param>
        private void button1_Click(object sender, EventArgs e)
        {
            // FormClosedEventArgsクラス作成
            // 引数に、フォームが閉じられた理由を渡す
            // (今回は「ウィンドウズがシャットダウンされるから」という理由にしてます)
            FormClosedEventArgs sample = new FormClosedEventArgs(CloseReason.WindowsShutDown);
            // フォームコントロールとFormClosedEventArgsオブジェクトを渡して呼び出し
            Form1_FormClosed(this, sample);
        }
    }
}

結果
event2.png

フォーム閉じる時の処理を呼び出せました!わーい!
ちなみに、これだけではフォーム閉じません。
ので、閉じるボタンとか押すとまたダイアログ開きます。楽しい。
なんとなくイベントハンドラに使われている引数の正体がわかりました。
一つ目の引数は、イベントの発生元コントロールが入る
二つ目の引数は発生したイベントの情報が入る。その情報というのはEventArgsクラスを継承したクラスのインスタンスである。

まとめ

デリゲートとは同じ戻り値の型/同じ引数の型を持つメソッドを代入できるもの。
イベントハンドラとはデリゲートの一種でありイベントが発生した時に行う処理のこと
Visual Studioで自動生成されたコード
引数の一つ目にはイベントの発生元コントロール
二つ目には、発生したイベントの情報が入る
その情報というのはEventArgsクラスを継承したクラスのインスタンスである

もしエラーが出る場合は
①デリゲートに入れるメソッドの型は、デリゲートの型と一致しているか?
②イベントハンドラの引数に、イベント発生元のコントロールオブジェクトが入っているか?
③発生したイベント情報(EventArgsクラスを継承したクラス)は意図していたイベント情報になっているか?
このあたりをチェックしてみてください。

おまけ(なんでこの記事書いたのか)

デリゲートとイベントハンドラの復習はできたでしょうか?
実は私が何に詰まっていたかというと、Timerクラスを使って定期的なイベントを発生させるところでした。なんかうまくエラー出る。
というわけで、まずは基礎の基礎のデリゲートとイベントハンドラ復習しよう!ってことで書きました。
問題が解決しなければ次回はこのTimerクラスについての記事を書こうと思います。
私自身が超初心者なので、間違った情報や補足情報があれば、ガンガンいただきたいです。

以上、ここまで読んでいただきありがとうございました。

Ich_wei_
C#とテスト
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした