C# EventArgsとは?イベント引数の基本から値の渡し方・カスタムEventArgsの作り方まで初心者向けに解説

はじめに

C#でイベント処理を学び始めると、よく登場するのがEventArgsです。ボタンがクリックされたとき、値が変更されたとき、処理が完了したときなど、C#では「何かが起きた」ことをイベントとして扱います。

そのイベントが発生したときに、イベントの詳細情報を渡すために使われるのがEventArgsです。

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

「EventArgsとは何をするもの?」
「senderとEventArgsは何が違うの?」
「イベントで文字列や数値を渡したいときはどうするの?」
「カスタムEventArgsはどう作ればいいの?」

この記事では、C#のEventArgsについて、基本的な役割から、値の渡し方、カスタムEventArgsの作り方、実践的なイベント実装例まで初心者向けにわかりやすく解説します。

1. C#のEventArgsとは?イベント引数の役割を初心者向けに解説

C#のEventArgsは、イベントが発生したときに、そのイベントに関する情報をイベントハンドラーへ渡すためのクラスです。

イベント処理では、多くの場合、次のような形のメソッドが使われます。

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

この第2引数にあるEventArgs eが、イベント引数です。

1-1. EventArgsはイベント発生時の情報を渡すためのクラス

EventArgsは、イベントに関連するデータを入れるための基本クラスです。

たとえば、ボタンがクリックされたイベントでは「クリックされた」という事実だけで十分な場合があります。その場合は、特別な情報を持たないEventArgsを使います。

一方で、次のような情報をイベント発生時に渡したい場合もあります。

C#
// 渡したい情報の例
string message = "処理が完了しました";
int progress = 100;
DateTime completedAt = DateTime.Now;

このような値をイベントで渡したい場合は、EventArgsを継承した独自クラス、つまりカスタムEventArgsを作ります。

つまり、EventArgsはイベント発生時に「何が起きたか」「どんなデータを渡すか」を表すための仕組みです。

1-2. イベントハンドラーの第2引数にEventArgsが使われる理由

C#のイベントハンドラーでは、一般的に次のような形が使われます。

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

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

第2引数のeには、イベントに関する追加情報が入ります。

この形に統一されていることで、C#ではイベント処理を一定のルールで書けます。

たとえば、イベントを受け取る側は、どのイベントでも基本的に次のように考えればよくなります。

C#
// sender: 誰がイベントを発生させたか
// e: イベントに関する情報

このような共通ルールがあるため、Windows Forms、WPF、ASP.NET、独自クラスのイベントなどでも同じ感覚でイベント処理を書けます。

1-3. senderとEventArgsの違い

senderEventArgsは、どちらもイベントハンドラーの引数ですが、役割が異なります。

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

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

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

C#
void Button_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
Console.WriteLine(button.Text);
}

一方、EventArgsはイベントに関する追加情報を表します。

つまり、違いは次のとおりです。

引数役割
senderイベントを発生させたオブジェクト
EventArgs eイベント発生時の追加情報

たとえば、「どのボタンがクリックされたか」はsenderから取得できます。

一方、「マウスの座標」「押されたキー」「変更されたプロパティ名」などはEventArgsまたはその派生クラスから取得します。

1-4. EventArgs.Emptyとは何か

EventArgs.Emptyは、追加情報がないイベントで使う空のEventArgsです。

たとえば、イベントを発生させるときに特別なデータを渡さない場合、次のように書けます。

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

new EventArgs()と書くこともできますが、追加情報がないことを明示する場合はEventArgs.Emptyがよく使われます。

C#
// 推奨されることが多い書き方
SomethingHappened?.Invoke(this, EventArgs.Empty);

// 毎回新しいインスタンスを作る書き方
SomethingHappened?.Invoke(this, new EventArgs());

EventArgs.Emptyを使うと、「このイベントには追加情報がありません」という意図がわかりやすくなります。

2. C#のイベント処理とEventArgsの基本構文

EventArgsを理解するには、C#のイベント処理全体の流れを知っておく必要があります。

C#のイベントは、主に次の要素で構成されます。

C#
// delegate: メソッドの形を定義する
// event: イベントを定義する
// EventHandler: よく使われる標準のイベント用デリゲート
// EventArgs: イベント引数

2-1. event・delegate・EventHandlerの関係

C#のイベントは、もともとdelegateを使って定義できます。

C#
public delegate void MyEventHandler(object sender, EventArgs e);

public event MyEventHandler SomethingHappened;

ただし、このようなイベント用のデリゲートは非常によく使われるため、C#には標準でEventHandlerが用意されています。

そのため、通常は次のように書けます。

C#
public event EventHandler SomethingHappened;

これは、次のような形のメソッドを登録できるイベントです。

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

つまり、EventHandlerを使うことで、自分でデリゲートを定義しなくても、標準的なイベントを簡単に作れます。

2-2. EventHandlerの基本形

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

C#
public event EventHandler EventName;

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

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

?.Invokeは、イベントにメソッドが登録されている場合だけ呼び出す書き方です。

もしイベントに誰も登録していない状態で直接呼び出すと、NullReferenceExceptionが発生する可能性があります。

C#
// 危険な可能性がある書き方
EventName(this, EventArgs.Empty);

// 安全な書き方
EventName?.Invoke(this, EventArgs.Empty);

そのため、イベントを発生させるときは?.Invokeを使うのが一般的です。

2-3. イベントを発生させる側と受け取る側の流れ

イベント処理には、イベントを発生させる側と、イベントを受け取る側があります。

たとえば、次のような流れです。

  1. イベントを持つクラスを作る

  2. イベントを発生させる

  3. 外部のクラスがイベントを購読する

  4. イベント発生時に登録されたメソッドが呼ばれる

コードで見ると、次のようになります。

C#
public class Worker
{
public event EventHandler WorkCompleted;

public void DoWork()
{
Console.WriteLine("作業中...");
WorkCompleted?.Invoke(this, EventArgs.Empty);
}
}

イベントを受け取る側は、次のようにイベントにメソッドを登録します。

C#
Worker worker = new Worker();

worker.WorkCompleted += OnWorkCompleted;

worker.DoWork();

void OnWorkCompleted(object sender, EventArgs e)
{
Console.WriteLine("作業が完了しました");
}

worker.DoWork()を実行すると、WorkCompletedイベントが発生し、OnWorkCompletedメソッドが呼ばれます。

2-4. EventArgsを使った最小コード例

EventArgsを使った最小限のイベント例は次のとおりです。

C#
using System;

public class Program
{
public static void Main()
{
var publisher = new Publisher();

publisher.SomethingHappened += OnSomethingHappened;

publisher.DoSomething();
}

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

public class Publisher
{
public event EventHandler SomethingHappened;

public void DoSomething()
{
Console.WriteLine("処理を実行します");

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

実行結果は次のようになります。

C#
処理を実行します
イベントを受け取りました

この例では、イベント発生時に追加データを渡していないため、EventArgs.Emptyを使っています。

3. EventArgsを使う場面と使わない場面

EventArgsはイベント処理でよく使われますが、すべてのイベントで複雑なEventArgsが必要になるわけではありません。

追加情報が必要ない場合はEventArgs.Emptyで十分です。

一方、イベント発生時に値を渡したい場合は、カスタムEventArgsを使います。

3-1. 追加情報が不要なイベントではEventArgs.Emptyを使う

たとえば、「処理が完了した」という事実だけを通知したい場合、追加情報は必要ありません。

C#
public event EventHandler Completed;

public void Complete()
{
Completed?.Invoke(this, EventArgs.Empty);
}

このようなイベントでは、EventArgs.Emptyを使えば十分です。

C#
void OnCompleted(object sender, EventArgs e)
{
Console.WriteLine("完了しました");
}

この場合、eから取得する情報はありません。

それでもEventArgsを使うのは、C#の標準的なイベントパターンに合わせるためです。

3-2. 値を渡したい場合はカスタムEventArgsを使う

イベント発生時に値を渡したい場合は、EventArgsを継承したクラスを作ります。

たとえば、メッセージを渡したい場合は次のようにします。

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

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

そして、イベント定義ではEventHandler<TEventArgs>を使います。

C#
public event EventHandler<MessageEventArgs> MessageReceived;

イベントを発生させるときに、カスタムEventArgsのインスタンスを渡します。

C#
MessageReceived?.Invoke(this, new MessageEventArgs("こんにちは"));

受け取る側では、e.Messageで値を取得できます。

C#
void OnMessageReceived(object sender, MessageEventArgs e)
{
Console.WriteLine(e.Message);
}

3-3. object senderだけで十分なケース

イベント発生元のオブジェクトだけがわかればよい場合は、senderだけで十分なこともあります。

たとえば、複数のボタンで同じイベントハンドラーを使う場合です。

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

void Button_Click(object sender, EventArgs e)
{
var button = (Button)sender;
Console.WriteLine($"{button.Text}がクリックされました");
}

この場合、どのボタンがクリックされたかはsenderから判断できます。

そのため、EventArgsに特別なデータを入れる必要はありません。

ただし、イベント発生元とは別の情報を渡したい場合は、senderではなくEventArgsを使うべきです。

3-4. EventArgsを継承する意味とメリット

カスタムEventArgsを作るときは、基本的にEventArgsを継承します。

C#
public class MyEventArgs : EventArgs
{
}

EventArgsを継承するメリットは、C#の標準的なイベントパターンに合わせられることです。

また、EventHandler<TEventArgs>と組み合わせて使いやすくなります。

C#
public event EventHandler<MyEventArgs> MyEvent;

独自のデリゲートを作らなくても、型安全にイベントデータを渡せるのが大きなメリットです。

4. C#のイベントで値を渡す方法

C#のイベントで値を渡したい場合、単純にEventArgsに値を追加することはできません。

標準のEventArgs自体は、追加データを持たないクラスです。

そのため、値を渡すにはカスタムEventArgsを作り、EventHandler<TEventArgs>を使います。

4-1. EventArgsだけでは任意の値を直接渡せない

次のように、通常のEventArgsへ自由に値を入れることはできません。

C#
EventArgs e = new EventArgs();

// e.Message = "こんにちは"; // これはできない

EventArgsには、MessageValueのようなプロパティは定義されていません。

そのため、イベントで値を渡したい場合は、独自のプロパティを持つクラスを作ります。

C#
public class DataEventArgs : EventArgs
{
public string Data { get; }

public DataEventArgs(string data)
{
Data = data;
}
}

このように、渡したいデータをプロパティとして用意します。

4-2. EventHandler<TEventArgs>を使って値を渡す

値を渡すイベントでは、EventHandler<TEventArgs>を使います。

C#
public event EventHandler<DataEventArgs> DataReceived;

DataEventArgsは、EventArgsを継承したクラスです。

C#
public class DataEventArgs : EventArgs
{
public string Data { get; }

public DataEventArgs(string data)
{
Data = data;
}
}

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

C#
DataReceived?.Invoke(this, new DataEventArgs("サンプルデータ"));

受け取る側では、e.Dataで値を取り出せます。

C#
void OnDataReceived(object sender, DataEventArgs e)
{
Console.WriteLine(e.Data);
}

4-3. イベント発生元から購読側へデータが渡る仕組み

イベントで値が渡る流れは次のとおりです。

まず、イベント発生元のクラスがカスタムEventArgsを作成します。

C#
var args = new DataEventArgs("イベントで渡す値");

次に、イベントを発火するときに、そのargsを第2引数として渡します。

C#
DataReceived?.Invoke(this, args);

すると、イベントを購読しているメソッドの第2引数に同じオブジェクトが渡されます。

C#
void OnDataReceived(object sender, DataEventArgs e)
{
Console.WriteLine(e.Data);
}

つまり、イベント発生元が作成したEventArgsオブジェクトを、イベント購読側が受け取る仕組みです。

4-4. 文字列・数値・オブジェクトを渡すサンプル

カスタムEventArgsを使えば、文字列、数値、オブジェクトなど複数の値をまとめて渡せます。

C#
using System;

public class User
{
public string Name { get; }
public int Age { get; }

public User(string name, int age)
{
Name = name;
Age = age;
}
}

public class UserEventArgs : EventArgs
{
public string Message { get; }
public int Count { get; }
public User User { get; }

public UserEventArgs(string message, int count, User user)
{
Message = message;
Count = count;
User = user;
}
}

public class UserService
{
public event EventHandler<UserEventArgs> UserCreated;

public void CreateUser()
{
var user = new User("田中", 30);

UserCreated?.Invoke(
this,
new UserEventArgs("ユーザーが作成されました", 1, user)
);
}
}

public class Program
{
public static void Main()
{
var service = new UserService();

service.UserCreated += OnUserCreated;

service.CreateUser();
}

private static void OnUserCreated(object sender, UserEventArgs e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.Count);
Console.WriteLine(e.User.Name);
Console.WriteLine(e.User.Age);
}
}

実行結果は次のようになります。

C#
ユーザーが作成されました
1
田中
30

このように、カスタムEventArgsを使うことで、イベント発生時に必要な情報をまとめて渡せます。

5. カスタムEventArgsの作り方

カスタムEventArgsは、イベントで独自の値を渡したいときに作成します。

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

C#
public class XxxEventArgs : EventArgs
{
public プロパティ名 { get; }

public XxxEventArgs( )
{
プロパティ名 = ;
}
}

5-1. EventArgsを継承したクラスを作成する

まず、EventArgsを継承したクラスを作ります。

C#
public class ProgressEventArgs : EventArgs
{
}

クラス名は、何のイベント引数なのかわかる名前にします。

たとえば、進捗を表すならProgressEventArgs、メッセージを表すならMessageEventArgsのようにします。

5-2. 渡したい値をプロパティとして定義する

次に、イベントで渡したい値をプロパティとして定義します。

C#
public class ProgressEventArgs : EventArgs
{
public int Progress { get; }
}

この例では、進捗率を表すProgressプロパティを用意しています。

複数の値を渡したい場合は、プロパティを複数定義します。

C#
public class ProgressEventArgs : EventArgs
{
public int Progress { get; }
public string Message { get; }
}

5-3. コンストラクターで値を受け取る

読み取り専用プロパティに値を設定するため、コンストラクターで値を受け取ります。

C#
public class ProgressEventArgs : EventArgs
{
public int Progress { get; }
public string Message { get; }

public ProgressEventArgs(int progress, string message)
{
Progress = progress;
Message = message;
}
}

このようにすると、インスタンス作成時に値を渡せます。

C#
var args = new ProgressEventArgs(50, "半分完了しました");

5-4. 読み取り専用プロパティにするべき理由

カスタムEventArgsのプロパティは、基本的に読み取り専用にするのがおすすめです。

C#
public int Progress { get; }

読み取り専用にすることで、イベントを受け取った側が値を勝手に変更できなくなります。

たとえば、次のようにsetを公開すると、イベントハンドラー側で値を書き換えられてしまいます。

C#
public int Progress { get; set; }

イベント引数は、イベント発生時の状態を表すデータとして扱うことが多いため、受け取る側が変更できないほうが安全です。

C#
public class ProgressEventArgs : EventArgs
{
public int Progress { get; }
public string Message { get; }

public ProgressEventArgs(int progress, string message)
{
Progress = progress;
Message = message;
}
}

このように、コンストラクターで値を設定し、プロパティはgetだけにする設計がよく使われます。

5-5. カスタムEventArgsの命名規則

カスタムEventArgsのクラス名は、最後にEventArgsを付けるのが一般的です。

C#
public class MessageEventArgs : EventArgs
{
}

public class ProgressEventArgs : EventArgs
{
}

public class UserCreatedEventArgs : EventArgs
{
}

イベント名と対応させると、よりわかりやすくなります。

C#
public event EventHandler<UserCreatedEventArgs> UserCreated;

この場合、イベント名はUserCreated、イベント引数はUserCreatedEventArgsです。

名前を見ただけで、どのイベントで使う引数なのかがわかります。

6. カスタムEventArgsを使ったイベント実装例

ここでは、カスタムEventArgsを使って、イベントで値を渡す実装例を見ていきます。

例として、「ダウンロードが完了したときにファイル名とサイズを通知するイベント」を作ります。

6-1. カスタムEventArgsクラスのコード例

まず、イベントで渡したいデータを持つカスタムEventArgsを作成します。

C#
using System;

public class DownloadCompletedEventArgs : EventArgs
{
public string FileName { get; }
public long FileSize { get; }

public DownloadCompletedEventArgs(string fileName, long fileSize)
{
FileName = fileName;
FileSize = fileSize;
}
}

このクラスでは、ファイル名を表すFileNameと、ファイルサイズを表すFileSizeを定義しています。

6-2. イベントを定義するコード例

次に、イベントを発生させるクラスを作ります。

C#
public class Downloader
{
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;
}

DownloadCompletedイベントでは、DownloadCompletedEventArgsを使ってデータを渡します。

通常のEventHandlerではなく、EventHandler<DownloadCompletedEventArgs>を使う点が重要です。

6-3. イベントを発火するコード例

イベントを発生させる処理を追加します。

C#
public class Downloader
{
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;

public void Download()
{
Console.WriteLine("ダウンロード中...");

string fileName = "sample.zip";
long fileSize = 2048;

DownloadCompleted?.Invoke(
this,
new DownloadCompletedEventArgs(fileName, fileSize)
);
}
}

DownloadCompleted?.Invoke(...)でイベントを発火しています。

第1引数のthisはイベント発生元、つまりDownloader自身です。

第2引数には、ファイル名とファイルサイズを持つDownloadCompletedEventArgsを渡しています。

6-4. イベントを購読して値を受け取るコード例

イベントを受け取る側は、次のように書きます。

C#
public class Program
{
public static void Main()
{
var downloader = new Downloader();

downloader.DownloadCompleted += OnDownloadCompleted;

downloader.Download();
}

private static void OnDownloadCompleted(
object sender,
DownloadCompletedEventArgs e)
{
Console.WriteLine("ダウンロードが完了しました");
Console.WriteLine($"ファイル名: {e.FileName}");
Console.WriteLine($"ファイルサイズ: {e.FileSize} bytes");
}
}

OnDownloadCompletedメソッドの第2引数がDownloadCompletedEventArgsになっているため、e.FileNamee.FileSizeで値を取得できます。

6-5. 実行結果と処理の流れ

全体のコードは次のようになります。

C#
using System;

public class DownloadCompletedEventArgs : EventArgs
{
public string FileName { get; }
public long FileSize { get; }

public DownloadCompletedEventArgs(string fileName, long fileSize)
{
FileName = fileName;
FileSize = fileSize;
}
}

public class Downloader
{
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;

public void Download()
{
Console.WriteLine("ダウンロード中...");

string fileName = "sample.zip";
long fileSize = 2048;

DownloadCompleted?.Invoke(
this,
new DownloadCompletedEventArgs(fileName, fileSize)
);
}
}

public class Program
{
public static void Main()
{
var downloader = new Downloader();

downloader.DownloadCompleted += OnDownloadCompleted;

downloader.Download();
}

private static void OnDownloadCompleted(
object sender,
DownloadCompletedEventArgs e)
{
Console.WriteLine("ダウンロードが完了しました");
Console.WriteLine($"ファイル名: {e.FileName}");
Console.WriteLine($"ファイルサイズ: {e.FileSize} bytes");
}
}

実行結果は次のとおりです。

C#
ダウンロード中...
ダウンロードが完了しました
ファイル名: sample.zip
ファイルサイズ: 2048 bytes

処理の流れは次のようになります。

まず、MainメソッドでDownloaderを作成します。

次に、DownloadCompletedイベントにOnDownloadCompletedメソッドを登録します。

その後、Download()を実行すると、ダウンロード処理の中でイベントが発火します。

イベント発火時にDownloadCompletedEventArgsが渡され、イベントハンドラー側でファイル名とファイルサイズを受け取ります。

7. EventHandlerとEventHandler<TEventArgs>の違い

C#のイベントでは、EventHandlerEventHandler<TEventArgs>の2つがよく使われます。

どちらもイベントを扱うための標準的なデリゲートですが、使う場面が異なります。

7-1. EventHandlerは追加データがないイベント向け

EventHandlerは、追加データがないイベントで使います。

C#
public event EventHandler Completed;

イベントを発生させるときは、EventArgs.Emptyを渡します。

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

受け取る側は次のようになります。

C#
void OnCompleted(object sender, EventArgs e)
{
Console.WriteLine("完了しました");
}

この場合、eには特別な情報は入っていません。

「イベントが発生したことだけを通知したい」場合に適しています。

7-2. EventHandler<TEventArgs>はデータを渡すイベント向け

EventHandler<TEventArgs>は、イベントでデータを渡したい場合に使います。

C#
public event EventHandler<MessageEventArgs> MessageReceived;

この場合、イベントハンドラーの第2引数はMessageEventArgsになります。

C#
void OnMessageReceived(object sender, MessageEventArgs e)
{
Console.WriteLine(e.Message);
}

カスタムEventArgsを使うことで、型安全に値を受け渡しできます。

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

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

7-3. カスタムデリゲートとの違い

C#では、自分でイベント用のデリゲートを定義することもできます。

C#
public delegate void MessageReceivedHandler(string message);

public event MessageReceivedHandler MessageReceived;

この場合、イベントハンドラーは次のような形になります。

C#
void OnMessageReceived(string message)
{
Console.WriteLine(message);
}

この書き方でも動作しますが、C#の標準的なイベントパターンからは外れます。

標準的なイベントパターンでは、次のようにsenderEventArgsを使います。

C#
public event EventHandler<MessageEventArgs> MessageReceived;

標準パターンにしておくと、他のC#開発者が見ても理解しやすく、フレームワークやライブラリの設計とも合わせやすくなります。

7-4. どちらを使うべきかの判断基準

EventHandlerEventHandler<TEventArgs>の使い分けは、次のように考えるとわかりやすいです。

目的使うもの
イベント発生だけを通知したいEventHandler
イベント発生時に値を渡したいEventHandler<TEventArgs>
特殊な引数形式にしたいカスタムデリゲート

基本的には、追加データがなければEventHandler、追加データがあればEventHandler<TEventArgs>を使うとよいでしょう。

独自のデリゲートは、どうしても標準形では表現しにくい場合に検討します。

8. EventArgsを使うときの注意点

EventArgsを使ったイベント処理は便利ですが、いくつか注意点があります。

特に、イベントの発火方法、プロパティ設計、購読解除などは重要です。

8-1. nullチェックをしてからイベントを発火する

イベントに誰も登録していない場合、そのイベントはnullになります。

その状態で直接イベントを呼び出すと、例外が発生する可能性があります。

C#
// 危険な書き方
Completed(this, EventArgs.Empty);

安全に呼び出すには、nullチェックを行います。

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

ただし、現在ではより簡潔な?.Invokeがよく使われます。

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

8-2. Invokeを使った安全なイベント呼び出し

イベントを発火するときは、次のようにInvokeを使います。

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

カスタムEventArgsを使う場合も同じです。

C#
ProgressChanged?.Invoke(
this,
new ProgressEventArgs(80, "もう少しで完了します")
);

?.Invokeを使うことで、イベントにハンドラーが登録されていない場合は何も実行されません。

そのため、イベント発火時の基本形として覚えておくと便利です。

8-3. EventArgsに処理ロジックを持たせすぎない

EventArgsは、イベント発生時のデータを渡すためのクラスです。

そのため、基本的にはデータを持つ役割に集中させるべきです。

たとえば、次のように処理ロジックを多く持たせる設計は避けたほうがよいでしょう。

C#
public class OrderEventArgs : EventArgs
{
public int OrderId { get; }

public void SaveToDatabase()
{
// データ保存処理
}

public void SendMail()
{
// メール送信処理
}
}

このようにすると、EventArgsの責務が大きくなりすぎます。

EventArgsにはイベントで渡したい情報を持たせ、実際の処理はイベントハンドラー側やサービスクラス側で行うほうが整理しやすくなります。

C#
public class OrderEventArgs : EventArgs
{
public int OrderId { get; }

public OrderEventArgs(int orderId)
{
OrderId = orderId;
}
}

8-4. イベント購読解除を忘れない

イベントを購読するときは、+=を使います。

C#
publisher.Completed += OnCompleted;

不要になったら、-=で購読解除できます。

C#
publisher.Completed -= OnCompleted;

イベントの購読解除を忘れると、オブジェクトが参照され続け、メモリリークの原因になることがあります。

特に、長く生きるオブジェクトのイベントを、短命なオブジェクトが購読する場合は注意が必要です。

たとえば、画面やコンポーネントを破棄するときにイベント購読を解除することがあります。

C#
public void Dispose()
{
publisher.Completed -= OnCompleted;
}

ラムダ式でイベントを購読した場合は、解除しにくくなることもあります。

C#
publisher.Completed += (sender, e) =>
{
Console.WriteLine("完了しました");
};

この書き方は簡単ですが、あとから同じラムダ式を指定して解除することができません。

解除が必要なイベントでは、名前付きメソッドを使うと管理しやすくなります。

8-5. 継承より既存のEventArgsクラスを使える場合もある

イベントで値を渡したい場合、すぐにカスタムEventArgsを作るのではなく、既存のEventArgs派生クラスが使えないか確認することも大切です。

たとえば、プロパティ変更通知ではPropertyChangedEventArgsがよく使われます。

C#
new PropertyChangedEventArgs(nameof(Name));

キャンセル可能なイベントではCancelEventArgsが使える場合があります。

C#
var args = new CancelEventArgs();

Windows FormsやWPFでは、マウス操作やキー入力に対応したMouseEventArgsKeyEventArgsも用意されています。

既存のクラスで意味が十分に表せるなら、新しく独自クラスを作らないほうがシンプルです。

9. よく使われるEventArgs派生クラス

C#や.NETには、EventArgsを継承した便利なクラスが多数用意されています。

代表的なものを知っておくと、自分でカスタムEventArgsを作るべきか判断しやすくなります。

9-1. CancelEventArgs

CancelEventArgsは、処理をキャンセルできるイベントで使われるクラスです。

C#
using System.ComponentModel;

public void OnClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
}

Cancelプロパティをtrueにすると、処理をキャンセルする意図を表せます。

たとえば、フォームを閉じる前に確認し、条件によって閉じる処理をキャンセルするような場面で使われます。

C#
void Form_Closing(object sender, CancelEventArgs e)
{
bool canClose = false;

if (!canClose)
{
e.Cancel = true;
}
}

9-2. PropertyChangedEventArgs

PropertyChangedEventArgsは、プロパティの値が変更されたことを通知するために使われます。

主にINotifyPropertyChangedと一緒に使います。

C#
using System.ComponentModel;

public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private string name;

public string Name
{
get => name;
set
{
if (name == value)
{
return;
}

name = value;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(Name))
);
}
}
}

PropertyChangedEventArgsには、変更されたプロパティ名が入ります。

WPFやMVVMなどでよく使われる重要なイベント引数です。

9-3. MouseEventArgs

MouseEventArgsは、マウス操作に関する情報を持つイベント引数です。

たとえば、クリック位置や押されたボタンなどを取得できます。

C#
void Control_MouseDown(object sender, MouseEventArgs e)
{
Console.WriteLine($"X座標: {e.X}");
Console.WriteLine($"Y座標: {e.Y}");
Console.WriteLine($"ボタン: {e.Button}");
}

Windows Formsなどでマウスイベントを扱うときによく使われます。

通常のEventArgsではマウス座標を取得できないため、MouseEventArgsのような専用クラスが用意されています。

9-4. KeyEventArgs

KeyEventArgsは、キーボード入力に関する情報を持つイベント引数です。

C#
void Control_KeyDown(object sender, KeyEventArgs e)
{
Console.WriteLine($"押されたキー: {e.KeyCode}");
}

どのキーが押されたか、CtrlキーやShiftキーが押されているかなどを確認できます。

C#
void Control_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.S)
{
Console.WriteLine("Ctrl + S が押されました");
}
}

キーボード操作を扱うイベントでは、通常のEventArgsではなくKeyEventArgsのような専用のイベント引数を使います。

9-5. 独自クラスを作る前に既存クラスを確認する

カスタムEventArgsは便利ですが、何でも独自クラスにすればよいわけではありません。

すでに目的に合ったEventArgs派生クラスが用意されている場合は、それを使うほうが自然です。

たとえば、次のように考えます。

目的使える可能性があるクラス
処理をキャンセルしたいCancelEventArgs
プロパティ変更を通知したいPropertyChangedEventArgs
マウス情報を扱いたいMouseEventArgs
キー入力を扱いたいKeyEventArgs
独自の業務データを渡したいカスタムEventArgs

独自のデータを渡す場合はカスタムEventArgsを作りますが、一般的なUI操作や通知では既存クラスを確認してから実装するとよいでしょう。

10. C# EventArgsに関するよくある質問

ここでは、C#のEventArgsについて初心者が疑問に感じやすいポイントをQ&A形式で解説します。

10-1. EventArgsは必ず継承しないといけない?

標準的なイベントパターンに従う場合、イベント引数クラスはEventArgsを継承するのが一般的です。

C#
public class MyEventArgs : EventArgs
{
}

特に、EventHandler<TEventArgs>を使う場合は、EventArgsを継承したクラスを使う設計にすると自然です。

ただし、C#では独自のデリゲートを定義して、EventArgsを使わないイベントを作ることもできます。

C#
public delegate void MessageHandler(string message);

public event MessageHandler MessageReceived;

このような書き方も可能ですが、一般的なC#のイベント設計では、senderEventArgsを使う形式がよく使われます。

迷った場合は、EventArgsを継承したカスタムクラスを使うとよいでしょう。

10-2. EventArgs.Emptyとnew EventArgs()の違いは?

EventArgs.Emptyは、追加情報がないことを表す共有インスタンスです。

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

一方、new EventArgs()は新しいEventArgsインスタンスを作成します。

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

どちらも「追加情報がないイベント引数」として使えます。

ただし、毎回新しいインスタンスを作る必要がない場合は、EventArgs.Emptyを使うほうが意図がわかりやすく、一般的です。

「このイベントでは追加データを渡しません」という意味を明確にしたい場合は、EventArgs.Emptyを使いましょう。

10-3. senderには何が入る?

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

たとえば、あるクラスの中で次のようにイベントを発火した場合です。

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

このとき、senderにはthis、つまりイベントを発生させたインスタンスが入ります。

受け取る側では、必要に応じて型変換できます。

C#
void OnCompleted(object sender, EventArgs e)
{
var worker = sender as Worker;

if (worker != null)
{
Console.WriteLine("Workerからイベントを受け取りました");
}
}

UIイベントの場合は、クリックされたボタンや操作されたコントロールがsenderに入ることが多いです。

C#
void Button_Click(object sender, EventArgs e)
{
var button = sender as Button;

if (button != null)
{
Console.WriteLine(button.Text);
}
}

10-4. 複数の値を渡すにはどうする?

複数の値を渡したい場合は、カスタムEventArgsに複数のプロパティを定義します。

C#
public class OrderCompletedEventArgs : EventArgs
{
public int OrderId { get; }
public string CustomerName { get; }
public decimal TotalAmount { get; }

public OrderCompletedEventArgs(
int orderId,
string customerName,
decimal totalAmount)
{
OrderId = orderId;
CustomerName = customerName;
TotalAmount = totalAmount;
}
}

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

C#
public event EventHandler<OrderCompletedEventArgs> OrderCompleted;

イベント発火時に、複数の値をまとめて渡します。

C#
OrderCompleted?.Invoke(
this,
new OrderCompletedEventArgs(1001, "佐藤", 9800)
);

受け取る側では、それぞれのプロパティから値を取得できます。

C#
void OnOrderCompleted(object sender, OrderCompletedEventArgs e)
{
Console.WriteLine(e.OrderId);
Console.WriteLine(e.CustomerName);
Console.WriteLine(e.TotalAmount);
}

このように、複数の値を渡したい場合は、1つのカスタムEventArgsにまとめるのが基本です。

10-5. カスタムEventArgsとラムダ式は一緒に使える?

カスタムEventArgsとラムダ式は一緒に使えます。

C#
downloader.DownloadCompleted += (sender, e) =>
{
Console.WriteLine(e.FileName);
Console.WriteLine(e.FileSize);
};

この場合、eDownloadCompletedEventArgs型として扱われます。

そのため、e.FileNamee.FileSizeにアクセスできます。

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

C#
service.MessageReceived += (sender, e) =>
{
Console.WriteLine($"受信メッセージ: {e.Message}");
};

ただし、イベント購読解除が必要な場合は注意が必要です。

ラムダ式を直接+=すると、あとから同じラムダ式を指定して解除するのが難しくなります。

購読解除が必要な場合は、名前付きメソッドを使うのがおすすめです。

C#
service.MessageReceived += OnMessageReceived;

service.MessageReceived -= OnMessageReceived;

void OnMessageReceived(object sender, MessageEventArgs e)
{
Console.WriteLine(e.Message);
}

まとめ

C#のEventArgsは、イベント発生時にイベントの情報を渡すための基本クラスです。

追加情報が不要なイベントでは、EventHandlerEventArgs.Emptyを使います。

C#
public event EventHandler Completed;

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

一方、イベント発生時に文字列、数値、オブジェクトなどの値を渡したい場合は、EventArgsを継承したカスタムEventArgsを作成し、EventHandler<TEventArgs>を使います。

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

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

public event EventHandler<MessageEventArgs> MessageReceived;

イベントを発火するときは、?.Invokeを使うと安全です。

C#
MessageReceived?.Invoke(this, new MessageEventArgs("こんにちは"));

senderはイベント発生元、EventArgsはイベントに関する追加情報という役割を持ちます。

項目役割
senderイベントを発生させたオブジェクト
EventArgsイベントに関する追加情報
EventArgs.Empty追加情報がないことを表す
カスタムEventArgs独自の値を渡すためのクラス
EventHandler<TEventArgs>カスタムEventArgsを使うイベント定義

C#でイベント処理を実装する際は、まず「イベント発生だけを通知したいのか」「イベントと一緒に値を渡したいのか」を考えることが大切です。

追加データがなければEventArgs.Empty、データを渡したいならカスタムEventArgsを使うと、読みやすく保守しやすいイベント設計になります。