C#のイベントとは?初心者がつまずく仕組み・使い方・実装例をわかりやすく解説
はじめに
C#のイベントは、初心者がつまずきやすい文法のひとつです。delegate、event、イベントハンドラー、+=、-=など、似たような用語や記号が一度に出てくるため、最初は「結局何をしているのか」が見えにくいかもしれません。
しかし、C#のイベントの考え方自体は難しくありません。イベントとは、簡単に言えば「何かが起きたときに、関係する処理へ知らせる仕組み」です。
たとえば、ボタンがクリックされたとき、タイマーが一定時間ごとに動いたとき、値が変更されたときなどに、C#ではイベントがよく使われます。この記事では、C#のイベントとは何か、デリゲートとの違い、基本構文、実装例、実務でよく使うパターン、注意点まで初心者向けにわかりやすく解説します。
1. C#のイベントとは?初心者向けに基本概念をわかりやすく解説
1-1. イベントは「何かが起きたことを知らせる仕組み」
C#のイベントとは、あるオブジェクトの中で「何かが起きた」ことを、別の処理に通知するための仕組みです。
たとえば、次のような場面を考えてみます。
C#button.Click += Button_Click;
これは「ボタンがクリックされたら、Button_Clickメソッドを実行する」という意味です。
ボタン自身は、クリックされたときにどのような処理を実行するかを詳しく知る必要はありません。ボタンはただ「クリックされた」というイベントを発生させます。そのイベントを受け取る側が、必要な処理を登録しておくことで、クリック時に処理が実行されます。
つまりイベントは、「発生する側」と「受け取る側」をつなぐ通知の仕組みです。
1-2. ボタンクリックや値の変更でイベントが使われる理由
C#では、Windows FormsやWPFなどのGUIアプリケーションでイベントが非常によく使われます。
代表的な例が、ボタンクリックです。
C#button.Click += Button_Click;
ユーザーがいつボタンをクリックするかは、プログラム側では事前に決められません。ユーザー操作によって発生するため、「クリックされたら処理する」というイベント駆動の考え方が向いています。
値の変更でもイベントは使われます。
たとえば、あるクラスの状態が変わったときに、画面表示やログ出力を自動的に更新したい場合があります。このようなときにイベントを使うと、「値が変わった」というタイミングで必要な処理を呼び出せます。
イベントは、次のような場面で便利です。
ユーザー操作に反応したいとき
状態変更を外部に通知したいとき
処理完了を別のクラスに知らせたいとき
複数の処理を同じタイミングで実行したいとき
1-3. イベントを使うと処理の呼び出し側と受け取り側を分離できる
イベントの大きなメリットは、処理を呼び出す側と受け取る側を分離できることです。
たとえば、注文処理を行うクラスがあるとします。注文が完了したときに、メール送信、ログ出力、在庫更新などを行いたい場合、注文クラスの中にすべての処理を直接書くと、クラス同士の依存関係が強くなります。
C#public class OrderService
{
public void CompleteOrder()
{
// 注文完了処理
// メール送信
// ログ出力
// 在庫更新
}
}
このように書くと、注文処理のクラスが多くの責任を持ってしまいます。
イベントを使うと、注文クラスは「注文が完了した」と通知するだけで済みます。
C#public class OrderService
{
public event EventHandler? OrderCompleted;
public void CompleteOrder()
{
Console.WriteLine("注文処理が完了しました。");
OrderCompleted?.Invoke(this, EventArgs.Empty);
}
}
このようにすると、メール送信やログ出力などの処理は、イベントを受け取る側で自由に登録できます。
1-4. C#におけるeventキーワードの役割
C#では、イベントを定義するときにeventキーワードを使います。
C#public event EventHandler? Completed;
eventキーワードを付けることで、そのメンバーは「イベント」として扱われます。
イベントは内部的にはデリゲートを利用していますが、外部から自由に代入したり、直接呼び出したりできないように制限されています。
たとえば、外部のクラスからできる操作は基本的に次の2つです。
C#obj.Completed += Handler;
obj.Completed -= Handler;
一方で、次のような操作は外部からはできません。
C#obj.Completed = Handler;
obj.Completed(null, EventArgs.Empty);
eventキーワードは、デリゲートを安全に公開するための仕組みだと考えると理解しやすくなります。
2. C#のイベントで初心者がつまずきやすい仕組み
2-1. イベント・デリゲート・イベントハンドラーの違い
C#のイベントを理解するうえで、特につまずきやすいのが「イベント」「デリゲート」「イベントハンドラー」の違いです。
まず、デリゲートはメソッドを参照するための型です。
C#public delegate void MyDelegate();
イベントは、そのデリゲートを使って「通知の口」を作る仕組みです。
C#public event MyDelegate? SomethingHappened;
イベントハンドラーは、イベントが発生したときに実行されるメソッドです。
C#void OnSomethingHappened()
{
Console.WriteLine("イベントを受け取りました。");
}
関係を整理すると、次のようになります。
| 用語 | 役割 |
|---|---|
| デリゲート | 登録できるメソッドの形を決める型 |
| イベント | 外部に通知するための仕組み |
| イベントハンドラー | イベント発生時に実行されるメソッド |
つまり、イベントは単独で存在するというより、デリゲートとイベントハンドラーを組み合わせて使う仕組みです。
2-2. 「イベントを発生させる側」と「イベントを受け取る側」の関係
イベントでは、「発生させる側」と「受け取る側」を分けて考えることが重要です。
たとえば、次のクラスはイベントを発生させる側です。
C#public class Notifier
{
public event EventHandler? Notified;
public void DoWork()
{
Console.WriteLine("処理を実行しました。");
Notified?.Invoke(this, EventArgs.Empty);
}
}
一方、イベントを受け取る側は、イベントハンドラーを登録します。
C#Notifier notifier = new Notifier();
notifier.Notified += (sender, e) =>
{
Console.WriteLine("通知を受け取りました。");
};
notifier.DoWork();
実行結果は次のようになります。
処理を実行しました。
通知を受け取りました。
このように、イベントを発生させる側は「通知するだけ」、受け取る側は「通知されたときの処理を決める」という役割分担になります。
2-3. +=で登録、-=で解除する仕組み
C#のイベントでは、+=を使ってイベントハンドラーを登録します。
C#notifier.Notified += OnNotified;
解除するときは-=を使います。
C#notifier.Notified -= OnNotified;
イベントには複数のイベントハンドラーを登録できます。
C#notifier.Notified += Handler1;
notifier.Notified += Handler2;
notifier.Notified += Handler3;
イベントが発生すると、登録されたイベントハンドラーが順番に呼び出されます。
イベントを使うときは、登録だけでなく解除も重要です。特に長く生き続けるオブジェクトのイベントに短命なオブジェクトが登録されている場合、解除し忘れるとメモリリークの原因になることがあります。
2-4. なぜ外部からイベントを直接呼び出せないのか
C#のイベントは、外部から直接呼び出せません。
たとえば、次のようなコードはコンパイルエラーになります。
C#notifier.Notified?.Invoke(null, EventArgs.Empty);
なぜなら、イベントを発生させる権限は、そのイベントを定義したクラス自身が持つべきだからです。
外部から自由にイベントを発生させられると、クラスの状態と無関係なタイミングで通知が行われてしまいます。たとえば「注文が完了していないのに、注文完了イベントだけ外部から発生させる」といった不自然な状態が起きてしまいます。
eventキーワードを使うことで、外部からは+=と-=による登録・解除だけを許可し、イベントの発生はクラス内部に限定できます。
2-5. nullチェックが必要になる理由
イベントにイベントハンドラーが1つも登録されていない場合、そのイベントはnullです。
その状態で直接呼び出すと、NullReferenceExceptionが発生します。
C#SomethingHappened(this, EventArgs.Empty); // 登録がないと例外になる
そのため、イベントを発生させるときはnullチェックが必要です。
現在のC#では、null条件演算子を使って次のように書くのが一般的です。
C#SomethingHappened?.Invoke(this, EventArgs.Empty);
これは「SomethingHappenedがnullでなければ呼び出す」という意味です。
3. C#イベントの基本構文と書き方
3-1. delegateを使ってイベントを宣言する基本形
イベントの基本形は、まずデリゲートを定義し、そのデリゲート型を使ってイベントを宣言します。
C#public delegate void MyEventHandler();
public class Sample
{
public event MyEventHandler? MyEvent;
public void Execute()
{
MyEvent?.Invoke();
}
}
使う側は、イベントハンドラーを登録します。
C#Sample sample = new Sample();
sample.MyEvent += () =>
{
Console.WriteLine("イベントが発生しました。");
};
sample.Execute();
実行結果は次のとおりです。
イベントが発生しました。
この形はイベントの仕組みを理解するにはわかりやすいですが、実務ではEventHandlerやEventHandler<TEventArgs>を使うことが多いです。
3-2. eventキーワードを使ったイベント定義
イベントを定義するときは、eventキーワードを付けます。
C#public event EventHandler? Completed;
これは「Completedというイベントを公開する」という意味です。
イベント名は、何が起きたのかがわかる名前にすると読みやすくなります。
C#public event EventHandler? Started;
public event EventHandler? Completed;
public event EventHandler? ValueChanged;
public event EventHandler? ErrorOccurred;
C#では、イベント名に過去形や「Changed」「Completed」などを使うことがよくあります。
3-3. イベントハンドラーを作成する方法
EventHandlerを使う場合、イベントハンドラーのシグネチャは次の形になります。
C#void Handler(object? sender, EventArgs e)
{
}
たとえば、次のように作成できます。
C#void OnCompleted(object? sender, EventArgs e)
{
Console.WriteLine("完了イベントを受け取りました。");
}
登録は次のように行います。
C#sample.Completed += OnCompleted;
ラムダ式を使って登録することもできます。
C#sample.Completed += (sender, e) =>
{
Console.WriteLine("ラムダ式でイベントを受け取りました。");
};
短い処理であればラムダ式は便利ですが、後で解除したい場合は名前付きメソッドを使う方が安全です。
3-4. イベントを発生させる処理の書き方
イベントを発生させるには、クラス内部からInvokeを呼び出します。
C#public class Worker
{
public event EventHandler? Completed;
public void Start()
{
Console.WriteLine("処理を開始しました。");
// 何らかの処理
Console.WriteLine("処理中...");
// イベントを発生させる
Completed?.Invoke(this, EventArgs.Empty);
}
}
thisはイベントを発生させたオブジェクト自身を表します。EventArgs.Emptyは、特に渡すデータがない場合に使います。
実務では、イベント発生用のメソッドを分けることもよくあります。
C#protected virtual void OnCompleted()
{
Completed?.Invoke(this, EventArgs.Empty);
}
そして、処理の中から呼び出します。
C#public void Start()
{
Console.WriteLine("処理を開始しました。");
OnCompleted();
}
このようにすると、イベント発生処理を整理しやすくなります。
3-5. EventHandlerとEventHandler<TEventArgs>の使い方
C#では、標準で用意されているEventHandlerを使うのが一般的です。
データを渡さないイベントでは、EventHandlerを使います。
C#public event EventHandler? Completed;
データを渡したい場合は、EventHandler<TEventArgs>を使います。
まず、イベント引数用のクラスを作成します。
C#public class ValueChangedEventArgs : EventArgs
{
public int OldValue { get; }
public int NewValue { get; }
public ValueChangedEventArgs(int oldValue, int newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
}
次に、イベントを定義します。
C#public event EventHandler<ValueChangedEventArgs>? ValueChanged;
イベント発生時にデータを渡します。
C#ValueChanged?.Invoke(this, new ValueChangedEventArgs(oldValue, newValue));
EventHandler<TEventArgs>を使うと、イベント発生時に必要な情報を安全に渡せます。
4. C#イベントの実装例で流れを理解する
4-1. 最小構成のイベント実装例
まずは、最小構成のイベント実装例を見てみましょう。
C#using System;
public class Program
{
public static void Main()
{
Publisher publisher = new Publisher();
publisher.MessageSent += OnMessageSent;
publisher.SendMessage();
}
static void OnMessageSent(object? sender, EventArgs e)
{
Console.WriteLine("イベントを受け取りました。");
}
}
public class Publisher
{
public event EventHandler? MessageSent;
public void SendMessage()
{
Console.WriteLine("メッセージを送信しました。");
MessageSent?.Invoke(this, EventArgs.Empty);
}
}
実行結果は次のようになります。
メッセージを送信しました。
イベントを受け取りました。
この例では、Publisherクラスがイベントを発生させる側です。ProgramクラスのOnMessageSentメソッドがイベントを受け取る側です。
4-2. ボタンが押されたような処理をイベントで再現する例
GUIのボタンクリックをイメージしたサンプルです。
C#using System;
public class Program
{
public static void Main()
{
MyButton button = new MyButton();
button.Clicked += (sender, e) =>
{
Console.WriteLine("ボタンがクリックされました。");
};
button.Click();
}
}
public class MyButton
{
public event EventHandler? Clicked;
public void Click()
{
Console.WriteLine("Clickメソッドが呼ばれました。");
Clicked?.Invoke(this, EventArgs.Empty);
}
}
実行結果は次のとおりです。
Clickメソッドが呼ばれました。
ボタンがクリックされました。
実際のWindows FormsやWPFでも、考え方はこれと同じです。ボタンがクリックされたときにClickイベントが発生し、登録されたイベントハンドラーが実行されます。
4-3. 値が変更されたときに通知するイベント実装例
次は、値が変更されたときにイベントを発生させる例です。
C#using System;
public class Program
{
public static void Main()
{
Counter counter = new Counter();
counter.ValueChanged += (sender, e) =>
{
Console.WriteLine("値が変更されました。");
};
counter.Value = 10;
counter.Value = 20;
}
}
public class Counter
{
private int _value;
public event EventHandler? ValueChanged;
public int Value
{
get { return _value; }
set
{
if (_value == value)
{
return;
}
_value = value;
ValueChanged?.Invoke(this, EventArgs.Empty);
}
}
}
実行結果は次のようになります。
値が変更されました。
値が変更されました。
値が同じ場合はイベントを発生させず、実際に変更されたときだけ通知しています。このような実装は、プロパティ変更通知でよく使われます。
4-4. イベント引数でデータを渡す実装例
イベント発生時に、変更前と変更後の値を渡す例です。
C#using System;
public class Program
{
public static void Main()
{
Counter counter = new Counter();
counter.ValueChanged += (sender, e) =>
{
Console.WriteLine($"値が {e.OldValue} から {e.NewValue} に変わりました。");
};
counter.Value = 10;
counter.Value = 25;
}
}
public class ValueChangedEventArgs : EventArgs
{
public int OldValue { get; }
public int NewValue { get; }
public ValueChangedEventArgs(int oldValue, int newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
}
public class Counter
{
private int _value;
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public int Value
{
get { return _value; }
set
{
if (_value == value)
{
return;
}
int oldValue = _value;
_value = value;
ValueChanged?.Invoke(this, new ValueChangedEventArgs(oldValue, _value));
}
}
}
実行結果は次のとおりです。
値が 0 から 10 に変わりました。
値が 10 から 25 に変わりました。
イベント引数を使うと、イベントを受け取る側が必要な情報を利用できます。
4-5. 複数のイベントハンドラーを登録する例
イベントには、複数のイベントハンドラーを登録できます。
C#using System;
public class Program
{
public static void Main()
{
Notifier notifier = new Notifier();
notifier.Notified += Handler1;
notifier.Notified += Handler2;
notifier.Notified += Handler3;
notifier.Notify();
}
static void Handler1(object? sender, EventArgs e)
{
Console.WriteLine("Handler1が実行されました。");
}
static void Handler2(object? sender, EventArgs e)
{
Console.WriteLine("Handler2が実行されました。");
}
static void Handler3(object? sender, EventArgs e)
{
Console.WriteLine("Handler3が実行されました。");
}
}
public class Notifier
{
public event EventHandler? Notified;
public void Notify()
{
Notified?.Invoke(this, EventArgs.Empty);
}
}
実行結果は次のようになります。
Handler1が実行されました。
Handler2が実行されました。
Handler3が実行されました。
このように、1つのイベントに対して複数の処理を登録できます。
5. イベントとデリゲートの違いを整理する
5-1. デリゲートはメソッドを参照する型
デリゲートは、メソッドを変数のように扱うための型です。
C#public delegate void MyDelegate();
public class Program
{
public static void Main()
{
MyDelegate del = SayHello;
del();
}
static void SayHello()
{
Console.WriteLine("こんにちは");
}
}
実行結果は次のとおりです。
こんにちは
デリゲートを使うと、メソッドそのものを引数として渡したり、変数に代入したりできます。
イベントは、このデリゲートの仕組みを利用して作られています。
5-2. イベントはデリゲートを安全に扱うための仕組み
イベントは、デリゲートを外部に公開するときに、安全に扱うための仕組みです。
デリゲートをそのままpublicフィールドとして公開すると、外部から自由に代入されたり、直接呼び出されたりしてしまいます。
C#public class Sample
{
public EventHandler? Completed;
}
この場合、外部から次のような操作ができます。
C#sample.Completed = SomeHandler;
sample.Completed?.Invoke(null, EventArgs.Empty);
これは危険です。外部から既存のイベントハンドラーを上書きしたり、不正なタイミングで呼び出したりできるからです。
そこで、eventを使います。
C#public class Sample
{
public event EventHandler? Completed;
}
eventを付けることで、外部からは登録と解除だけが可能になります。
5-3. public delegateフィールドとの違い
publicなデリゲートフィールドとeventの違いは、外部からできる操作にあります。
C#public class BadSample
{
public EventHandler? Completed;
}
public class GoodSample
{
public event EventHandler? Completed;
}
BadSampleでは、外部から代入できます。
C#bad.Completed = Handler;
この代入によって、それまで登録されていた他のハンドラーが消えてしまう可能性があります。
一方、GoodSampleでは次のような代入はできません。
C#good.Completed = Handler; // コンパイルエラー
できるのは、登録と解除です。
C#good.Completed += Handler;
good.Completed -= Handler;
この制限が、イベントを安全に使うために重要です。
5-4. eventを付けることで制限される操作
eventを付けると、外部からは次の操作が制限されます。
C#sample.Completed = Handler; // できない
sample.Completed = null; // できない
sample.Completed?.Invoke(this, e); // できない
一方で、次の操作はできます。
C#sample.Completed += Handler; // 登録できる
sample.Completed -= Handler; // 解除できる
イベントを定義したクラスの内部では、イベントを発生させることができます。
C#Completed?.Invoke(this, EventArgs.Empty);
つまり、イベントは「外部には登録・解除だけを許可し、発生は内部に限定する」ための仕組みです。
5-5. デリゲートを理解してからイベントを学ぶべき理由
C#のイベントは、内部的にデリゲートを使っています。そのため、デリゲートを理解しているとイベントの仕組みがかなりわかりやすくなります。
特に、次の点を押さえておくと理解が進みます。
デリゲートはメソッドを参照できる
デリゲートには複数のメソッドを登録できる
登録されたメソッドは順番に実行される
イベントはデリゲートを安全に公開する仕組みである
ただし、初心者が最初からデリゲートを深く理解しようとすると難しく感じることもあります。まずは「イベントは何かが起きたことを知らせる仕組み」と捉え、サンプルコードを動かしながら少しずつ理解していくのがおすすめです。
6. 実務でよく使うC#イベントのパターン
6-1. Windows FormsやWPFのClickイベント
C#のイベントで最もよく目にするのが、Windows FormsやWPFのClickイベントです。
Windows Formsでは、ボタンのクリックイベントを次のように書きます。
C#button1.Click += button1_Click;
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("ボタンがクリックされました。");
}
WPFでも考え方は同じです。
C#private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("ボタンがクリックされました。");
}
GUIアプリケーションでは、ユーザーの操作に応じて処理を実行するため、イベントが非常に重要です。
6-2. TimerのElapsedイベント
一定時間ごとに処理を実行したい場合、Timerのイベントが使われます。
C#using System;
using System.Timers;
public class Program
{
public static void Main()
{
Timer timer = new Timer(1000);
timer.Elapsed += OnElapsed;
timer.Start();
Console.ReadLine();
}
private static void OnElapsed(object? sender, ElapsedEventArgs e)
{
Console.WriteLine("1秒経過しました。");
}
}
Elapsedイベントは、指定した間隔が経過するたびに発生します。
タイマー処理は、定期実行、監視処理、自動更新などでよく使われます。
6-3. PropertyChangedイベントとINotifyPropertyChanged
WPFやMVVMでは、INotifyPropertyChangedインターフェイスがよく使われます。
これは、プロパティの値が変更されたことを通知するための仕組みです。
C#using System.ComponentModel;
using System.Runtime.CompilerServices;
public class Person : INotifyPropertyChanged
{
private string _name = "";
public event PropertyChangedEventHandler? PropertyChanged;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
PropertyChangedイベントを使うことで、画面側は値の変更を検知して表示を更新できます。
6-4. 独自クラスでイベント通知を実装するケース
実務では、自分で作ったクラスにイベントを持たせることもあります。
たとえば、ファイルの読み込みが完了したときに通知するクラスです。
C#public class FileLoader
{
public event EventHandler? LoadCompleted;
public void Load()
{
Console.WriteLine("ファイルを読み込み中...");
// 読み込み処理
LoadCompleted?.Invoke(this, EventArgs.Empty);
}
}
使う側は次のように登録します。
C#FileLoader loader = new FileLoader();
loader.LoadCompleted += (sender, e) =>
{
Console.WriteLine("読み込み完了後の処理を実行します。");
};
loader.Load();
独自イベントを使うと、クラスの内部処理と外部の反応をきれいに分離できます。
6-5. 非同期処理や処理完了通知でイベントを使うケース
時間のかかる処理が完了したときに通知したい場合にも、イベントは役立ちます。
C#public class Downloader
{
public event EventHandler? DownloadCompleted;
public async Task DownloadAsync()
{
Console.WriteLine("ダウンロード開始");
await Task.Delay(2000);
Console.WriteLine("ダウンロード完了");
DownloadCompleted?.Invoke(this, EventArgs.Empty);
}
}
使う側は、完了イベントを受け取って次の処理を実行できます。
C#Downloader downloader = new Downloader();
downloader.DownloadCompleted += (sender, e) =>
{
Console.WriteLine("完了通知を受け取りました。");
};
await downloader.DownloadAsync();
ただし、近年のC#では非同期処理にはasync/awaitやTaskを使うことが多く、イベントは「複数の受け取り側へ通知したい場合」や「状態変化を通知したい場合」に使われることが多いです。
7. C#イベント実装時の注意点とよくあるエラー
7-1. イベントハンドラーのシグネチャが合わない
イベントハンドラーは、イベントの型に合ったメソッドでなければ登録できません。
EventHandlerの場合、メソッドは次の形である必要があります。
C#void Handler(object? sender, EventArgs e)
{
}
たとえば、次のようなメソッドは登録できません。
C#void Handler()
{
}
イベントの型とメソッドの引数・戻り値が一致していないと、コンパイルエラーになります。
EventHandler<TEventArgs>を使う場合は、第2引数の型にも注意が必要です。
C#void OnValueChanged(object? sender, ValueChangedEventArgs e)
{
}
7-2. イベントがnullで呼び出し時に例外が発生する
イベントに何も登録されていない状態で呼び出すと、NullReferenceExceptionが発生します。
悪い例は次のとおりです。
C#Completed(this, EventArgs.Empty);
安全に呼び出すには、次のように書きます。
C#Completed?.Invoke(this, EventArgs.Empty);
古い書き方では、次のようにnullチェックすることもあります。
C#if (Completed != null)
{
Completed(this, EventArgs.Empty);
}
現在は、?.Invokeを使う書き方が簡潔でわかりやすいです。
7-3. -=で解除し忘れてメモリリークにつながる
イベントを登録したまま解除し忘れると、メモリリークにつながることがあります。
特に、長く生き続けるオブジェクトのイベントに、短命なオブジェクトがイベントハンドラーを登録している場合は注意が必要です。
C#publisher.SomeEvent += subscriber.HandleEvent;
この状態では、publisherがsubscriberを参照し続ける可能性があります。そのため、subscriberが不要になってもガベージコレクションの対象になりにくくなる場合があります。
不要になったら解除します。
C#publisher.SomeEvent -= subscriber.HandleEvent;
画面やコンポーネントの破棄時には、イベント解除を忘れないようにしましょう。
7-4. ラムダ式で登録したイベントを解除できない
ラムダ式でイベントを登録すると、解除しにくい場合があります。
C#button.Click += (sender, e) =>
{
Console.WriteLine("クリックされました。");
};
このように直接ラムダ式を書くと、同じラムダ式を-=で指定しても基本的には解除できません。
解除したい場合は、変数に保持します。
C#EventHandler handler = (sender, e) =>
{
Console.WriteLine("クリックされました。");
};
button.Click += handler;
button.Click -= handler;
または、名前付きメソッドを使います。
C#button.Click += OnButtonClick;
button.Click -= OnButtonClick;
イベント解除が必要な場面では、名前付きメソッドを使う方がわかりやすく安全です。
7-5. イベントを発生させる場所が適切でない
イベントは、適切なタイミングで発生させる必要があります。
たとえば、値が変更されていないのにValueChangedイベントを発生させると、受け取る側が不要な処理を実行してしまいます。
悪い例です。
C#set
{
_value = value;
ValueChanged?.Invoke(this, EventArgs.Empty);
}
この場合、同じ値を設定してもイベントが発生します。
改善例です。
C#set
{
if (_value == value)
{
return;
}
_value = value;
ValueChanged?.Invoke(this, EventArgs.Empty);
}
イベントは「本当に通知すべき状態変化が起きたとき」に発生させることが大切です。
8. 初心者が理解しやすいイベントの学習ステップ
8-1. まずはイベント駆動の考え方を理解する
C#イベントを理解するには、まずイベント駆動の考え方を押さえることが重要です。
イベント駆動とは、「何かが起きたら処理を実行する」という考え方です。
たとえば、次のような処理です。
ボタンがクリックされたらメッセージを表示する
値が変わったら画面を更新する
ダウンロードが終わったら通知する
エラーが発生したらログを出力する
通常のメソッド呼び出しは、呼び出す側が明確に処理を実行します。一方、イベントでは「発生したタイミングに応じて、登録された処理が実行される」という流れになります。
8-2. delegateの基本を押さえる
イベントはデリゲートを使っているため、デリゲートの基本を知っておくと理解しやすくなります。
最低限、次の内容を理解しておきましょう。
C#public delegate void MyDelegate();
MyDelegate del = SomeMethod;
del();
デリゲートは、メソッドを参照して呼び出せる型です。
イベントは、このデリゲートに対して「外部から勝手に呼び出せない」「勝手に上書きできない」という制限を加えたものと考えるとわかりやすいです。
8-3. eventキーワードの制約を理解する
eventキーワードのポイントは、外部からの操作を制限することです。
外部からできる操作は次の2つです。
C#obj.SomeEvent += Handler;
obj.SomeEvent -= Handler;
外部からできない操作は次のようなものです。
C#obj.SomeEvent = Handler;
obj.SomeEvent = null;
obj.SomeEvent?.Invoke(obj, EventArgs.Empty);
この制約により、イベントを発生させる責任をクラス内部に限定できます。
初心者はまず、「eventを付けると外部から直接呼び出せなくなる」と覚えるとよいでしょう。
8-4. 小さなサンプルを自分で書いて動かす
イベントは、読むだけでは理解しにくい部分があります。小さなサンプルを自分で書いて動かすのが効果的です。
最初は次のような簡単なコードで十分です。
C#using System;
public class Program
{
public static void Main()
{
Alarm alarm = new Alarm();
alarm.Rang += (sender, e) =>
{
Console.WriteLine("アラームが鳴りました。");
};
alarm.Ring();
}
}
public class Alarm
{
public event EventHandler? Rang;
public void Ring()
{
Console.WriteLine("Ringメソッドを実行しました。");
Rang?.Invoke(this, EventArgs.Empty);
}
}
このサンプルで、次の流れを確認しましょう。
イベントを定義する
イベントハンドラーを登録する
メソッド内でイベントを発生させる
登録した処理が実行される
この流れが理解できれば、C#イベントの基本はかなり身につきます。
8-5. Windows FormsやWPFの既存イベントで練習する
基本を理解したら、Windows FormsやWPFの既存イベントで練習すると実践的です。
たとえば、Windows Formsではボタンを配置して、クリックイベントを登録します。
C#private void button1_Click(object sender, EventArgs e)
{
label1.Text = "クリックされました";
}
GUIアプリケーションでは、イベントの流れを目で確認しやすいため、初心者の学習に向いています。
また、WPFではINotifyPropertyChangedを使うことで、プロパティ変更通知の実務的なイベントパターンも学べます。
9. C#イベントに関するよくある質問
9-1. イベントとメソッド呼び出しは何が違う?
メソッド呼び出しは、呼び出す相手を直接指定して処理を実行します。
C#service.SendMail();
一方、イベントは「何かが起きた」と通知し、登録されている処理を実行します。
C#Completed?.Invoke(this, EventArgs.Empty);
イベントでは、発生させる側が受け取り側の具体的な処理を知る必要がありません。そのため、クラス同士の結びつきを弱くできます。
直接呼び出すべき処理ならメソッド、状態変化や操作を外部に知らせたい場合はイベントを使うと考えるとよいでしょう。
9-2. eventキーワードは必ず必要?
C#で「イベント」として安全に公開するなら、eventキーワードを使うべきです。
デリゲートフィールドをpublicにすれば似たようなことはできますが、外部から代入や直接呼び出しができてしまうため危険です。
C#public EventHandler? Completed; // 推奨されない
イベントとして公開するなら、次のように書きます。
C#public event EventHandler? Completed;
eventキーワードを使うことで、外部からは登録と解除だけに制限できます。
9-3. イベントハンドラーは複数登録できる?
はい、C#のイベントには複数のイベントハンドラーを登録できます。
C#sample.Completed += Handler1;
sample.Completed += Handler2;
sample.Completed += Handler3;
イベントが発生すると、登録されたイベントハンドラーが順番に実行されます。
複数の処理を同じタイミングで実行したい場合に便利です。
9-4. イベントの登録順に処理は実行される?
通常、イベントハンドラーは登録された順に実行されます。
C#sample.Completed += Handler1;
sample.Completed += Handler2;
sample.Completed += Handler3;
この場合、一般的にはHandler1、Handler2、Handler3の順で実行されます。
ただし、イベントハンドラー同士が実行順に強く依存する設計は避けた方がよいです。順番が重要な処理であれば、イベントではなく明示的なメソッド呼び出しや処理フローとして実装する方が安全です。
9-5. ActionやFuncでイベントの代わりにできる?
ActionやFuncはデリゲート型なので、イベントに似た使い方はできます。
C#public Action? Completed;
しかし、このようにpublicで公開すると、外部から代入や直接呼び出しができてしまいます。
C#sample.Completed = null;
sample.Completed?.Invoke();
安全に通知の仕組みとして公開したい場合は、eventを使う方が適切です。
C#public event Action? Completed;
ただし、C#の一般的なイベントパターンでは、EventHandlerまたはEventHandler<TEventArgs>を使うことが多いです。
C#public event EventHandler? Completed;
特別な理由がなければ、標準的なイベントパターンに合わせると、他の開発者にも理解されやすいコードになります。
まとめ
C#のイベントとは、「何かが起きたことを外部に知らせる仕組み」です。ボタンクリック、値の変更、タイマー、処理完了通知など、さまざまな場面で使われます。
イベントを理解するうえで重要なのは、次のポイントです。
イベントは通知の仕組みである
デリゲートはメソッドを参照する型である
イベントハンドラーはイベント発生時に実行されるメソッドである
+=で登録し、-=で解除するeventを付けると外部から直接呼び出せなくなるイベントを発生させるときは
?.Invokeを使うデータを渡す場合は
EventHandler<TEventArgs>を使う
初心者のうちは、delegateやeventという言葉だけを見ると難しく感じるかもしれません。しかし、実際の考え方は「起きたことを知らせて、登録された処理を実行する」というシンプルなものです。
まずは小さなサンプルを書き、イベントを定義する、イベントハンドラーを登録する、イベントを発生させるという流れを手で確認してみましょう。C#のイベントを理解できると、Windows Forms、WPF、MVVM、独自クラスの通知処理など、実務で使う多くのコードが読みやすくなります。

