C#イベントハンドラとは?書き方・使い方・+=の仕組みを初心者向けにわかりやすく解説

はじめに

C#でWindows FormsやWPF、ASP.NETなどを学び始めると、必ずといってよいほど登場するのが「イベントハンドラ」です。

たとえば、ボタンをクリックしたときに処理を実行する、テキストボックスの入力内容が変わったときに画面を更新する、ファイルが作成されたときに通知を受け取る、といった処理はイベントハンドラによって実現されます。

しかし初心者のうちは、次のような疑問を持ちやすいです。

「イベントハンドラとは何か」
「なぜ += で登録するのか」
object senderEventArgs e は何を意味しているのか」
「event、delegate、EventHandlerの違いがわからない」

この記事では、C#イベントハンドラの基本から、書き方、使い方、+= の仕組み、実務での注意点まで、初心者にもわかりやすく順番に解説します。

1. C#イベントハンドラとは?初心者がまず押さえる基本

1-1. イベントハンドラは「イベントが起きたときに実行されるメソッド」

C#イベントハンドラとは、簡単にいうと「何かのイベントが発生したときに自動で実行されるメソッド」のことです。

イベントとは、プログラムの中で起きる出来事を指します。

たとえば、次のようなものがイベントです。

ユーザーがボタンをクリックした
テキストボックスの文字が変更された
フォームが読み込まれた
ファイルが作成された
タイマーの時間が経過した

これらのイベントが発生したときに、どの処理を実行するかを指定するのがイベントハンドラです。

たとえば、ボタンがクリックされたときにメッセージを表示したい場合は、「クリックイベント」に対して「メッセージを表示するメソッド」を登録します。この登録されたメソッドがイベントハンドラです。

1-2. ボタンクリック・入力変更・ファイル検知など身近な使用例

C#イベントハンドラは、GUIアプリケーションで特によく使われます。

Windows Formsでは、ボタンのClickイベントにイベントハンドラを登録して、クリック時の処理を書きます。

C#
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("ボタンがクリックされました");
}

WPFでは、XAMLでボタンのClickイベントにメソッドを指定することがあります。

XML
<Button Content="保存" Click="SaveButton_Click" />
C#
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
// 保存処理
}

また、ファイル監視を行う FileSystemWatcher でもイベントハンドラが使われます。

C#
watcher.Created += OnFileCreated;

private void OnFileCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"ファイルが作成されました: {e.Name}");
}

このように、C#イベントハンドラは「何かが起きたら処理する」という場面で非常によく使われます。

1-3. イベント・イベントハンドラ・イベント発生元の関係

イベントハンドラを理解するには、次の3つの関係を押さえることが大切です。

イベント発生元は、イベントを発生させるオブジェクトです。たとえばボタンやテキストボックス、タイマーなどです。

イベントは、発生元が通知する出来事です。たとえばClick、TextChanged、Tickなどがあります。

イベントハンドラは、イベントが発生したときに実行されるメソッドです。

具体例で考えてみましょう。

C#
button1.Click += button1_Click;

この場合、button1 がイベント発生元、Click がイベント、button1_Click がイベントハンドラです。

つまり、「button1のClickイベントが発生したら、button1_Clickメソッドを実行する」という意味になります。

1-4. 通常のメソッドとの違いは「呼び出されるタイミング」

イベントハンドラも、見た目は通常のメソッドとほとんど同じです。

C#
private void ShowMessage()
{
Console.WriteLine("こんにちは");
}
C#
private void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("クリックされました");
}

どちらもメソッドですが、大きな違いは呼び出されるタイミングです。

通常のメソッドは、自分で明示的に呼び出します。

C#
ShowMessage();

一方、イベントハンドラはイベントが発生したときに呼び出されます。

C#
button1.Click += button1_Click;

このコードを書いておくと、ユーザーがボタンをクリックしたタイミングで button1_Click が実行されます。

つまり、イベントハンドラは「自分で直接呼ぶメソッド」というより、「イベントに反応して呼ばれるメソッド」と考えると理解しやすいです。

1-5. C#でイベントハンドラを使うメリット

C#イベントハンドラを使うメリットは、処理をイベントごとに分離できることです。

たとえば、ボタンがクリックされたときの処理、入力内容が変わったときの処理、画面が読み込まれたときの処理を、それぞれ別のメソッドとして書けます。

これにより、コードの役割がわかりやすくなります。

また、イベントを発生させる側と、イベントを受け取って処理する側を分けられる点も大きなメリットです。

たとえば、自作クラスで「処理が完了した」というイベントを公開しておけば、そのクラスを使う側は完了時の処理を自由に登録できます。

このように、C#イベントハンドラは、画面操作や非同期処理、通知処理などを柔軟に扱うために欠かせない仕組みです。

2. C#イベントハンドラの基本的な書き方

2-1. イベントハンドラの基本構文

C#イベントハンドラの基本的な形は次のとおりです。

C#
private void イベントハンドラ名(object sender, EventArgs e)
{
// イベント発生時に実行したい処理
}

たとえば、ボタンのクリックイベントに対応するイベントハンドラは次のように書きます。

C#
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("クリックされました");
}

このメソッドをイベントに登録するには、次のように += を使います。

C#
button1.Click += button1_Click;

これで、button1 がクリックされたときに button1_Click が実行されます。

2-2. object sender と EventArgs e の意味

C#イベントハンドラでよく見る引数が、object senderEventArgs e です。

C#
private void button1_Click(object sender, EventArgs e)
{
}

sender は、イベントを発生させたオブジェクトを表します。

たとえば、ボタンがクリックされた場合、sender にはクリックされたボタンが入ります。

C#
private void button1_Click(object sender, EventArgs e)
{
Button clickedButton = (Button)sender;
clickedButton.Text = "クリック済み";
}

このように、複数のボタンで同じイベントハンドラを使う場合に、どのボタンから呼ばれたのかを判断できます。

e は、イベントに関する追加情報を持つオブジェクトです。

標準的なイベントでは EventArgs が使われますが、イベントの種類によっては、より具体的な型が使われます。

たとえば、マウスイベントでは MouseEventArgs、キー入力イベントでは KeyEventArgs、ファイル作成イベントでは FileSystemEventArgs などが使われます。

C#
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
MessageBox.Show("Enterキーが押されました");
}
}

つまり、sender は「誰がイベントを起こしたか」、e は「イベントに関する詳しい情報」と考えるとわかりやすいです。

2-3. Visual Studioで自動生成されるイベントハンドラの例

Visual StudioでWindows Formsアプリを作成している場合、フォーム上のボタンをダブルクリックすると、イベントハンドラが自動生成されます。

たとえば、button1 をダブルクリックすると、次のようなコードが作られます。

C#
private void button1_Click(object sender, EventArgs e)
{
}

さらに、フォームのデザイナー側では、次のような登録処理が自動的に追加されます。

C#
this.button1.Click += new System.EventHandler(this.button1_Click);

最近のC#では、次のように短く書くこともできます。

C#
button1.Click += button1_Click;

どちらも意味はほぼ同じです。

初心者のうちは、Visual Studioが自動生成したコードを見て「なぜ知らないメソッドが勝手に呼ばれるのか」と感じるかもしれません。しかし実際には、裏側でイベントにイベントハンドラが登録されているため、クリック時にそのメソッドが実行されます。

2-4. ボタンクリックイベントのサンプルコード

ここでは、Windows Formsでボタンをクリックしたときにメッセージを表示する例を見てみましょう。

C#
using System;
using System.Windows.Forms;

public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();

button1.Click += button1_Click;
}

private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("ボタンがクリックされました");
}
}

このコードでは、コンストラクタ内で button1.Clickbutton1_Click を登録しています。

C#
button1.Click += button1_Click;

この1行によって、ボタンがクリックされたときに button1_Click が呼び出されるようになります。

イベントハンドラの中では、クリックされたときに実行したい処理を書きます。

C#
MessageBox.Show("ボタンがクリックされました");

このように、C#イベントハンドラは「イベントにメソッドを登録する」「イベント発生時にそのメソッドが実行される」という流れで使います。

2-5. メソッド名の付け方と読みやすい命名ルール

イベントハンドラのメソッド名は自由に付けられますが、何のイベントに対応しているのかがわかる名前にすることが大切です。

Windows Formsでは、次のような名前がよく使われます。

C#
button1_Click
textBox1_TextChanged
MainForm_Load

ただし、button1textBox1 のような名前は、画面部品が増えると意味がわかりにくくなります。

実務では、次のように役割がわかる名前にする方が読みやすくなります。

C#
saveButton_Click
userNameTextBox_TextChanged
mainForm_Load

また、処理内容を重視して次のように書くこともあります。

C#
OnSaveButtonClick
OnUserNameChanged
OnMainFormLoaded

大切なのは、後から読んだときに「どのイベントに対応している処理なのか」「何をする処理なのか」がわかることです。

3. +=でイベントハンドラを登録する仕組み

3-1. +=はイベントに処理を追加するための演算子

C#イベントハンドラでは、イベントにメソッドを登録するときに += を使います。

C#
button1.Click += button1_Click;

これは、「button1のClickイベントに、button1_Clickという処理を追加する」という意味です。

初心者のうちは、なぜ = ではなく += を使うのか疑問に感じるかもしれません。

ポイントは、イベントには複数のイベントハンドラを登録できるということです。

C#
button1.Click += ShowMessage;
button1.Click += WriteLog;
button1.Click += UpdateStatus;

このように、1つのイベントに対して複数のメソッドを追加できます。

そのため、イベントハンドラの登録では「代入する」というより「追加する」という考え方になります。

3-2. なぜ代入ではなく+=を使うのか

もしイベントハンドラを = で代入できると、すでに登録されているイベントハンドラを上書きしてしまう可能性があります。

たとえば、次のようなイメージです。

C#
button1.Click = button1_Click;

イベントでは通常、このような直接代入はできません。

イベントは、複数の処理を安全に登録できるように設計されています。そのため、登録には +=、解除には -= を使います。

+= を使うことで、既存のイベントハンドラを消さずに、新しいイベントハンドラを追加できます。

C#
button1.Click += FirstHandler;
button1.Click += SecondHandler;

この場合、Clickイベントが発生すると、FirstHandlerSecondHandler の両方が呼び出されます。

3-3. 複数のイベントハンドラを登録できる仕組み

C#のイベントは、内部的にはデリゲートという仕組みを使っています。

デリゲートは、メソッドを参照できる型です。そして、デリゲートには複数のメソッドをまとめて登録できます。

これをマルチキャストデリゲートと呼びます。

たとえば、次のように複数のイベントハンドラを登録できます。

C#
button1.Click += HandlerA;
button1.Click += HandlerB;
button1.Click += HandlerC;

この状態でボタンをクリックすると、登録されたイベントハンドラが順番に呼び出されます。

C#
private void HandlerA(object sender, EventArgs e)
{
Console.WriteLine("A");
}

private void HandlerB(object sender, EventArgs e)
{
Console.WriteLine("B");
}

private void HandlerC(object sender, EventArgs e)
{
Console.WriteLine("C");
}

出力は次のようになります。

C#
A
B
C

このように、+= はイベントに処理を追加していくための重要な仕組みです。

3-4. 登録されたイベントハンドラが実行される順番

通常、複数のイベントハンドラを += で登録した場合、登録した順番に実行されます。

C#
button1.Click += HandlerA;
button1.Click += HandlerB;

この場合、Clickイベントが発生すると、まず HandlerA が実行され、その後に HandlerB が実行されます。

ただし、実務では「イベントハンドラの実行順に強く依存する設計」は避けた方がよいです。

なぜなら、後から別のイベントハンドラが追加されたり、登録順が変更されたりすると、動作がわかりにくくなるからです。

順番が重要な処理は、1つのイベントハンドラの中で明示的に順番を管理する方が安全です。

C#
private void button1_Click(object sender, EventArgs e)
{
ValidateInput();
SaveData();
ShowCompleteMessage();
}

このように書けば、処理の順番がコード上で明確になります。

3-5. -=でイベントハンドラを解除する方法

登録したイベントハンドラは、-= を使って解除できます。

C#
button1.Click -= button1_Click;

これは、「button1のClickイベントからbutton1_Clickを外す」という意味です。

たとえば、一度だけ実行したいイベントハンドラでは、実行後に自分自身を解除することがあります。

C#
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("一度だけ実行されます");

button1.Click -= button1_Click;
}

この場合、最初のクリックではメッセージが表示されますが、2回目以降は表示されません。

また、イベントハンドラを解除しないと、不要な参照が残り、メモリリークの原因になることがあります。特に、長く生きるオブジェクトのイベントに、短命なオブジェクトのメソッドを登録する場合は注意が必要です。

4. event・delegate・EventHandlerの関係をわかりやすく整理

4-1. delegateは「メソッドを入れられる型」

C#イベントハンドラを理解するうえで、delegateの考え方はとても重要です。

delegateは、「メソッドを変数のように扱うための型」です。

たとえば、次のようなdelegateを定義できます。

C#
public delegate void MyDelegate(string message);

これは、「string型の引数を1つ受け取り、戻り値がvoidのメソッドを参照できる型」という意味です。

次のようなメソッドを代入できます。

C#
public void ShowMessage(string message)
{
Console.WriteLine(message);
}
C#
MyDelegate del = ShowMessage;
del("こんにちは");

このように、delegateを使うと、メソッドそのものを変数のように扱えます。

イベントは、このdelegateの仕組みを使って実現されています。

4-2. eventは外部から安全に通知を受け取るための仕組み

eventは、クラスの外部に対して「この出来事が起きたら通知します」と公開するための仕組みです。

たとえば、処理が完了したときに通知するイベントを作る場合、次のように書けます。

C#
public event EventHandler Completed;

このイベントを使う側は、+= でイベントハンドラを登録できます。

C#
worker.Completed += Worker_Completed;

一方で、イベントを発生させる処理は、基本的にイベントを定義しているクラスの内部から行います。

C#
Completed?.Invoke(this, EventArgs.Empty);

eventを使うことで、外部のクラスはイベントハンドラの登録や解除はできますが、勝手にイベントを発生させることはできません。

これにより、クラスの内部状態を守りながら、安全に通知の仕組みを提供できます。

4-3. EventHandlerはC#でよく使われる標準デリゲート

C#では、イベント用の標準デリゲートとして EventHandler が用意されています。

EventHandler は、次のような形のメソッドを登録できるデリゲートです。

C#
void Handler(object sender, EventArgs e)

つまり、次のようなイベントハンドラに対応しています。

C#
private void Sample_EventOccurred(object sender, EventArgs e)
{
}

イベントを定義する側は、次のように書けます。

C#
public event EventHandler SomethingHappened;

イベントを発生させるときは、次のように書きます。

C#
SomethingHappened?.Invoke(this, EventArgs.Empty);

EventHandler を使うことで、C#で一般的なイベントの形に沿ったコードを書けます。

4-4. EventHandler<TEventArgs>を使うケース

イベントに追加情報を渡したい場合は、EventHandler<TEventArgs> を使います。

たとえば、処理完了時に結果メッセージを渡したい場合は、独自のEventArgsクラスを作成します。

C#
public class CompletedEventArgs : EventArgs
{
public string Message { get; }

public CompletedEventArgs(string message)
{
Message = message;
}
}

イベントは次のように定義します。

C#
public event EventHandler<CompletedEventArgs> Completed;

イベントを発生させるときは、追加情報を渡します。

C#
Completed?.Invoke(this, new CompletedEventArgs("処理が完了しました"));

受け取る側は、e.Message から情報を取得できます。

C#
private void Worker_Completed(object sender, CompletedEventArgs e)
{
Console.WriteLine(e.Message);
}

このように、イベントに詳細な情報を持たせたい場合は、EventHandler<TEventArgs> を使うのが一般的です。

4-5. eventとdelegateの違いを初心者向けに比較

delegateとeventは似ていますが、役割が異なります。

delegateは、メソッドを参照するための型です。メソッドを変数のように代入したり、呼び出したりできます。

eventは、そのdelegateを使って、外部に通知を公開するための仕組みです。

初心者向けにたとえるなら、delegateは「呼び出すメソッドの形を決めるもの」、eventは「そのメソッドを登録してもらうための窓口」です。

C#
public delegate void MyDelegate();

public event MyDelegate MyEvent;

この例では、MyDelegate がメソッドの形を決め、MyEvent が外部に公開されるイベントになります。

実務では、自分でdelegateを定義するよりも、EventHandlerEventHandler<TEventArgs> を使うことが多いです。

5. C#イベントハンドラの使い方をサンプルで理解する

5-1. Windows FormsのClickイベントで使う例

Windows Formsでは、C#イベントハンドラを最も直感的に理解できます。

次の例では、ボタンをクリックするとラベルの文字を変更します。

C#
using System;
using System.Windows.Forms;

public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();

saveButton.Click += SaveButton_Click;
}

private void SaveButton_Click(object sender, EventArgs e)
{
resultLabel.Text = "保存しました";
}
}

このコードでは、saveButton.ClickSaveButton_Click を登録しています。

ユーザーが保存ボタンをクリックすると、SaveButton_Click が実行され、ラベルに「保存しました」と表示されます。

Windows Formsでは、Click、TextChanged、Load、CheckedChangedなど、多くのイベントでイベントハンドラを使います。

5-2. WPFやASP.NETで使われるイベントハンドラの例

WPFでもイベントハンドラはよく使われます。

XAMLで次のように書くと、ボタンのClickイベントにイベントハンドラを指定できます。

XML
<Button Content="検索" Click="SearchButton_Click" />

コードビハインドでは、次のようにイベントハンドラを書きます。

C#
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
// 検索処理
}

WPFでは、EventArgs ではなく RoutedEventArgs が使われることがあります。これは、WPFのルーティングイベントという仕組みに対応しているためです。

ASP.NET Web Formsでも、ボタンクリックなどにイベントハンドラを使います。

C#
protected void SubmitButton_Click(object sender, EventArgs e)
{
// 送信処理
}

このように、C#イベントハンドラはデスクトップアプリだけでなく、Webアプリの一部でも使われます。

5-3. 自作クラスでイベントを定義する例

C#では、既存のUI部品だけでなく、自分で作ったクラスにもイベントを定義できます。

たとえば、作業が完了したときに通知するクラスを作ってみます。

C#
using System;

public class Worker
{
public event EventHandler Completed;

public void DoWork()
{
Console.WriteLine("作業中...");

OnCompleted();
}

protected virtual void OnCompleted()
{
Completed?.Invoke(this, EventArgs.Empty);
}
}

このクラスでは、Completed というイベントを定義しています。

C#
public event EventHandler Completed;

作業が完了したタイミングで、OnCompleted メソッドからイベントを発生させています。

C#
Completed?.Invoke(this, EventArgs.Empty);

このように、自作クラスでもeventを使うことで、処理完了や状態変更を外部に通知できます。

5-4. イベントを発生させる側と受け取る側のコード

次に、イベントを発生させる側と受け取る側を分けて見てみましょう。

イベントを発生させる側です。

C#
public class Worker
{
public event EventHandler Completed;

public void DoWork()
{
Console.WriteLine("処理を開始します");

// 何らかの処理
Console.WriteLine("処理が完了しました");

Completed?.Invoke(this, EventArgs.Empty);
}
}

イベントを受け取る側です。

C#
public class Program
{
public static void Main()
{
Worker worker = new Worker();

worker.Completed += Worker_Completed;

worker.DoWork();
}

private static void Worker_Completed(object sender, EventArgs e)
{
Console.WriteLine("完了イベントを受け取りました");
}
}

このコードを実行すると、worker.DoWork() の中で処理が完了したタイミングで Completed イベントが発生し、登録されている Worker_Completed が呼び出されます。

このように、イベントを発生させる側は「通知するだけ」、受け取る側は「通知を受けて処理する」という形で役割を分けられます。

5-5. ラムダ式でイベントハンドラを書く方法

イベントハンドラは、名前付きメソッドだけでなく、ラムダ式で書くこともできます。

C#
button1.Click += (sender, e) =>
{
MessageBox.Show("クリックされました");
};

短い処理であれば、ラムダ式を使うとコードを簡潔に書けます。

たとえば、コンソールアプリで自作イベントにラムダ式を登録する例です。

C#
Worker worker = new Worker();

worker.Completed += (sender, e) =>
{
Console.WriteLine("ラムダ式で完了イベントを受け取りました");
};

worker.DoWork();

ただし、ラムダ式を使う場合は、イベント解除に注意が必要です。

次のように書くと、同じ見た目でも別のラムダ式として扱われるため、解除できません。

C#
button1.Click += (sender, e) => Console.WriteLine("クリック");
button1.Click -= (sender, e) => Console.WriteLine("クリック");

イベント解除が必要な場合は、ラムダ式を変数に保持しておく必要があります。

C#
EventHandler handler = (sender, e) =>
{
Console.WriteLine("クリック");
};

button1.Click += handler;
button1.Click -= handler;

ラムダ式は便利ですが、解除が必要な場面では注意して使いましょう。

6. 初心者がつまずきやすいポイントとエラー対策

6-1. イベントハンドラが呼ばれない原因

C#イベントハンドラが呼ばれない場合、よくある原因はイベントに登録されていないことです。

イベントハンドラのメソッドを書いただけでは、イベント発生時に自動で呼ばれるわけではありません。

C#
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("クリック");
}

このメソッドを作っただけでは不十分です。

次のように、イベントに登録する必要があります。

C#
button1.Click += button1_Click;

Visual Studioのデザイナーでイベントを設定している場合は、自動で登録されます。しかし、手動でコードを書いている場合は、+= の書き忘れに注意しましょう。

また、別のボタンに登録していた、イベント名を間違えていた、対象のコントロールが無効化されていた、といった原因もあります。

6-2. 引数の型や戻り値が合わないエラー

イベントハンドラは、イベントが要求するメソッドの形と一致している必要があります。

たとえば、Clickイベントでは通常、次の形のメソッドを使います。

C#
private void button1_Click(object sender, EventArgs e)
{
}

戻り値は void、引数は object senderEventArgs e です。

もし次のように戻り値や引数が違うと、登録できません。

C#
private int button1_Click(object sender, EventArgs e)
{
return 0;
}

また、引数が足りない場合もエラーになります。

C#
private void button1_Click()
{
}

イベントハンドラが登録できない場合は、対象イベントが求める引数の型と戻り値を確認しましょう。

6-3. +=を書き忘れた場合の挙動

+= を書き忘れると、イベントハンドラは呼ばれません。

たとえば、次のようにメソッドだけを定義していても、クリック時には何も起きません。

C#
private void SaveButton_Click(object sender, EventArgs e)
{
Console.WriteLine("保存");
}

必要なのは、イベントへの登録です。

C#
saveButton.Click += SaveButton_Click;

Visual Studioのデザイナーを使っている場合、見た目上はボタンとメソッドが関連しているように見えても、デザイナーコードから登録が外れているとイベントハンドラは呼ばれません。

イベントが呼ばれないときは、まず += で登録されているかを確認しましょう。

6-4. -=しないことで起きるメモリリークの注意点

イベントハンドラを登録すると、イベント発生元は登録されたイベントハンドラへの参照を持ちます。

そのため、長く生きるオブジェクトのイベントに、短い寿命のオブジェクトのメソッドを登録したままにすると、不要になったオブジェクトが解放されないことがあります。

たとえば、アプリケーション全体で使われるサービスのイベントに、画面のイベントハンドラを登録した場合です。

C#
service.Updated += OnServiceUpdated;

画面を閉じても、serviceOnServiceUpdated を参照し続けていると、画面オブジェクトがメモリに残る可能性があります。

そのため、不要になったタイミングで解除します。

C#
service.Updated -= OnServiceUpdated;

Windows Formsならフォームの破棄時、WPFなら画面のUnload時などに解除することがあります。

C#
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
service.Updated -= OnServiceUpdated;
}

すべてのイベントで必ず解除が必要というわけではありませんが、イベント発生元の寿命が長い場合は特に注意しましょう。

6-5. ラムダ式のイベント解除で注意すべきこと

ラムダ式でイベントハンドラを登録するときは、解除方法に注意が必要です。

次のコードは、一見すると登録と解除が対応しているように見えます。

C#
button1.Click += (sender, e) => Console.WriteLine("クリック");
button1.Click -= (sender, e) => Console.WriteLine("クリック");

しかし、この2つのラムダ式は別のインスタンスとして扱われるため、解除できません。

解除したい場合は、ラムダ式を変数に代入します。

C#
EventHandler clickHandler = (sender, e) =>
{
Console.WriteLine("クリック");
};

button1.Click += clickHandler;
button1.Click -= clickHandler;

短い処理で解除が不要な場合はラムダ式でも問題ありませんが、解除が必要なイベントでは名前付きメソッドを使うか、ラムダ式を変数に保持するのが安全です。

6-6. senderとeを使わない場合はどうするか

イベントハンドラでは、sendere を使わないこともよくあります。

C#
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("クリックされました");
}

この例では、sendere も使っていません。

使わない場合でも、イベントが要求するメソッドの形に合わせる必要があるため、引数自体は残しておきます。

警告を避けたい場合や、使わないことを明示したい場合は、ラムダ式で _ を使うこともあります。

C#
button1.Click += (_, _) =>
{
Console.WriteLine("クリックされました");
};

ただし、通常のイベントハンドラでは object sender, EventArgs e のままで問題ありません。

7. C#イベントハンドラを実務で使うときのベストプラクティス

7-1. 処理が長い場合はイベントハンドラを短く保つ

実務では、イベントハンドラの中に長い処理を書きすぎないことが大切です。

たとえば、ボタンクリック時に入力チェック、データ保存、ログ出力、画面更新などをすべて直接書くと、イベントハンドラが読みにくくなります。

悪い例です。

C#
private void saveButton_Click(object sender, EventArgs e)
{
if (nameTextBox.Text == "")
{
MessageBox.Show("名前を入力してください");
return;
}

// 保存処理
// ログ出力
// 画面更新
// その他の処理
}

イベントハンドラは、できるだけ処理の流れを示す役割にして、具体的な処理は別メソッドに分けると読みやすくなります。

C#
private void saveButton_Click(object sender, EventArgs e)
{
if (!ValidateInput())
{
return;
}

SaveUser();
ShowCompleteMessage();
}

このようにすると、イベントハンドラの役割が明確になります。

7-2. イベントハンドラ内にビジネスロジックを書きすぎない

イベントハンドラにビジネスロジックを書きすぎると、コードの再利用やテストが難しくなります。

たとえば、売上計算や在庫更新などの業務処理をボタンクリックイベントの中に直接書くと、その処理を別の画面やバッチ処理で使い回しにくくなります。

イベントハンドラは、画面操作を受け取り、必要な処理を呼び出す入口として考えるとよいです。

C#
private void calculateButton_Click(object sender, EventArgs e)
{
var amount = decimal.Parse(amountTextBox.Text);

var result = taxService.CalculateWithTax(amount);

resultLabel.Text = result.ToString();
}

この例では、税計算そのものは taxService に任せています。

このように、イベントハンドラとビジネスロジックを分離すると、保守しやすいコードになります。

7-3. nullチェックをして安全にイベントを発生させる

自作イベントを発生させるときは、イベントハンドラが1つも登録されていない可能性があります。

その状態でイベントを呼び出すと、NullReferenceExceptionが発生します。

古い書き方では、次のようにnullチェックをしていました。

C#
if (Completed != null)
{
Completed(this, EventArgs.Empty);
}

現在では、null条件演算子を使って次のように書くのが一般的です。

C#
Completed?.Invoke(this, EventArgs.Empty);

この書き方なら、Completed にイベントハンドラが登録されている場合だけ呼び出されます。登録されていない場合は何も起きません。

自作イベントでは、次のように Onイベント名 というメソッドを用意することもよくあります。

C#
protected virtual void OnCompleted()
{
Completed?.Invoke(this, EventArgs.Empty);
}

イベントを発生させる処理を1か所にまとめることで、保守しやすくなります。

7-4. 非同期処理でイベントハンドラを使うときの注意点

C#では、イベントハンドラを async にすることもあります。

たとえば、ボタンクリック時に非同期でデータを保存する場合です。

C#
private async void saveButton_Click(object sender, EventArgs e)
{
await SaveAsync();

MessageBox.Show("保存しました");
}

イベントハンドラでは、戻り値が void である必要があるため、async void になることがあります。

ただし、async void は例外処理が難しくなるため、イベントハンドラ以外では基本的に避けるべきです。

イベントハンドラ内で非同期処理を行う場合は、try-catchを使って例外を処理しましょう。

C#
private async void saveButton_Click(object sender, EventArgs e)
{
try
{
await SaveAsync();
MessageBox.Show("保存しました");
}
catch (Exception ex)
{
MessageBox.Show($"エラーが発生しました: {ex.Message}");
}
}

また、時間のかかる処理をUIスレッドで直接実行すると、画面が固まる原因になります。非同期処理を使って、UIの応答性を保つことが大切です。

7-5. 読みやすく保守しやすいコード例

最後に、イベントハンドラを短く保ち、処理を分離したコード例を見てみましょう。

C#
private async void saveButton_Click(object sender, EventArgs e)
{
if (!ValidateInput())
{
return;
}

await SaveUserAsync();

ShowSavedMessage();
}

private bool ValidateInput()
{
if (string.IsNullOrWhiteSpace(nameTextBox.Text))
{
MessageBox.Show("名前を入力してください");
return false;
}

return true;
}

private async Task SaveUserAsync()
{
var user = new User
{
Name = nameTextBox.Text
};

await userService.SaveAsync(user);
}

private void ShowSavedMessage()
{
MessageBox.Show("保存しました");
}

この例では、イベントハンドラの中では大まかな流れだけを書いています。

入力チェック、保存処理、メッセージ表示を別メソッドに分けているため、読みやすく、修正もしやすくなります。

C#イベントハンドラを実務で使うときは、「イベントハンドラは入口」「具体的な処理は別メソッドや別クラスに任せる」と考えると、保守しやすいコードになります。

8. C#イベントハンドラに関するよくある質問

8-1. イベントハンドラは必ずvoidで書く必要がある?

多くのC#イベントハンドラは、戻り値を void で書きます。

たとえば、標準的な EventHandler は次の形です。

C#
void Handler(object sender, EventArgs e)

そのため、Windows FormsのClickイベントなどでは、イベントハンドラの戻り値は void である必要があります。

C#
private void button1_Click(object sender, EventArgs e)
{
}

イベントは、何かが起きたことを通知する仕組みであり、戻り値を受け取って処理を分岐する用途にはあまり向いていません。

独自のdelegateを定義すれば戻り値のあるイベントのような仕組みも作れますが、一般的なC#イベントハンドラでは void を使うと考えて問題ありません。

8-2. senderには何が入る?

sender には、イベントを発生させたオブジェクトが入ります。

たとえば、ボタンのClickイベントであれば、クリックされたボタンが sender に入ります。

C#
private void button_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
MessageBox.Show(button.Text);
}

複数のボタンで同じイベントハンドラを使う場合、sender を使うと、どのボタンがクリックされたのかを判別できます。

C#
button1.Click += CommonButton_Click;
button2.Click += CommonButton_Click;

private void CommonButton_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
MessageBox.Show($"{button.Text} がクリックされました");
}

このように、sender はイベント発生元を知りたいときに便利です。

8-3. EventArgs.Emptyはいつ使う?

EventArgs.Empty は、イベントに追加情報がない場合に使います。

自作イベントを発生させるとき、特に渡す情報がなければ次のように書きます。

C#
Completed?.Invoke(this, EventArgs.Empty);

new EventArgs() と書くこともできますが、追加情報がないことを表すには EventArgs.Empty を使うのが一般的です。

C#
Completed?.Invoke(this, new EventArgs());

上記よりも、次の方が意図がわかりやすくなります。

C#
Completed?.Invoke(this, EventArgs.Empty);

イベントに独自の情報を渡したい場合は、EventArgs を継承したクラスを作成し、EventHandler<TEventArgs> を使います。

8-4. イベントハンドラを直接呼び出してもいい?

イベントハンドラは普通のメソッドなので、技術的には直接呼び出すこともできます。

C#
button1_Click(this, EventArgs.Empty);

しかし、基本的にはおすすめしません。

イベントハンドラは「イベントが発生したときに呼ばれる処理」として設計されているため、直接呼び出すと意図がわかりにくくなることがあります。

同じ処理を別の場所からも使いたい場合は、共通処理を別メソッドに切り出すのがよいです。

C#
private void button1_Click(object sender, EventArgs e)
{
Save();
}

private void Save()
{
// 保存処理
}

こうすれば、イベントハンドラからも、他の処理からも Save メソッドを呼び出せます。

8-5. +=と-=はどんな場面で使い分ける?

+= は、イベントにイベントハンドラを登録するときに使います。

C#
button1.Click += button1_Click;

-= は、登録したイベントハンドラを解除するときに使います。

C#
button1.Click -= button1_Click;

通常、画面の初期化時やオブジェクト作成時に += で登録し、不要になったタイミングで -= で解除します。

たとえば、フォームが開いたときにイベントを登録し、閉じるときに解除するような使い方です。

C#
private void MainForm_Load(object sender, EventArgs e)
{
service.Updated += OnServiceUpdated;
}

private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
service.Updated -= OnServiceUpdated;
}

特に、イベント発生元の寿命が長い場合は、解除し忘れに注意しましょう。

8-6. delegateを知らなくてもイベントハンドラは使える?

delegateを深く理解していなくても、C#イベントハンドラを使うことはできます。

初心者のうちは、まず次の流れを覚えれば十分です。

イベントがある
イベントに += でメソッドを登録する
イベントが発生すると登録したメソッドが呼ばれる

たとえば、ボタンクリックなら次のように書ければ問題ありません。

C#
button1.Click += button1_Click;

private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("クリックされました");
}

ただし、event、delegate、EventHandlerの関係を理解すると、自作イベントを作ったり、複雑なイベント処理を読み解いたりしやすくなります。

最初は「イベントハンドラはイベント発生時に動くメソッド」と理解し、慣れてきたらdelegateやeventの仕組みを学ぶとよいでしょう。

まとめ

C#イベントハンドラとは、イベントが発生したときに実行されるメソッドです。

ボタンクリック、入力変更、フォーム読み込み、タイマー、ファイル検知など、C#のさまざまな場面でイベントハンドラは使われます。

基本的な形は次のとおりです。

C#
private void Handler(object sender, EventArgs e)
{
// イベント発生時の処理
}

イベントに登録するときは += を使います。

C#
button1.Click += button1_Click;

解除するときは -= を使います。

C#
button1.Click -= button1_Click;

sender にはイベントを発生させたオブジェクトが入り、e にはイベントに関する追加情報が入ります。

また、C#のイベントはdelegateの仕組みを使っており、eventによって外部から安全にイベントハンドラを登録・解除できるようになっています。

初心者のうちは、まず「イベントハンドラはイベントが起きたときに呼ばれるメソッド」「+= はイベントに処理を追加する記号」と覚えると理解しやすいです。

慣れてきたら、EventHandlerEventHandler<TEventArgs>、自作イベント、ラムダ式での登録、-= による解除なども学んでいくと、C#イベントハンドラをより実務的に使えるようになります。