デリゲートとイベント
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 43/70。
デリゲートは、メソッドへの参照を表す型です。データではなくメソッドを保持する変数だと考えてください。これにより、メソッドを引数として渡したり、コレクションに格納したり、動的に呼び出したりすることができます。
デリゲートを宣言するには、それが参照できるメソッドシグネチャを指定します。
public delegate void Notify(string message);
public class Publisher
{
public void SendAlert(string msg) => Console.WriteLine("Alert: " + msg);
}
// 使い方
Notify notifier = new Publisher().SendAlert;
notifier("System started"); // Alert: System startedイベントはデリゲートを基盤として、パブリッシュ/サブスクライブ パターンを実装します。あるクラスがイベントを発生させ、他のクラスはその通知を受け取るために購読(サブスクライブ)できます。event キーワードはデリゲートの使用方法を制限します。購読者はハンドラーの追加や削除のみが可能で、イベントを直接呼び出すことはできません。
public class Button
{
public event Action Clicked;
public void Click()
{
Clicked?.Invoke(); // すべての購読者に通知
}
}
// 購読(サブスクライブ)
Button btn = new Button();
btn.Clicked += () => Console.WriteLine("Button was clicked!");
btn.Click(); // ボタンがクリックされました!+= 演算子はハンドラーを登録し、-= は登録を解除します。?.Invoke() パターンは、サブスクライバーが存在する場合にのみ、安全にイベントを呼び出します。イベントは、GUIアプリケーション、ゲームエンジン、およびオブジェクトが密結合することなく変更に反応する必要があるあらゆるシステムにおいて不可欠なものです。
チャレンジ
簡単温度が変化するたびにサブスクライバーに通知するために、デリゲートとイベントを使用する温度監視システムを構築しましょう。これは、イベントが非常にエレガントに実現するパブリッシュ/サブスクライブパターンの典型的な例です。
コードは2つのファイルに分けて構成します:
TemperatureMonitor.cs:Monitoring名前空間内に、パブリッシャーとして機能するTemperatureMonitorクラスを作成します。モニターは以下の機能を持つ必要があります:- 現在の温度を保存するためのプライベートフィールドを持つ
- 温度が更新されるたびに発生する、
Action<int>を使用したTemperatureChangedという名前のイベントを宣言する Temperatureプロパティ(int)を持ち、そのセッターでプライベートフィールドを更新し、新しい温度値でイベントを発生させる
?.Invoke()パターンを使用することを忘れないでください。Program.cs: メインファイルで、TemperatureMonitorを作成し、そのTemperatureChangedイベントに2つの異なるハンドラーをサブスクライブさせます:Alert: Temperature is now {value} degreesと出力するハンドラーLogged: {value}と出力するハンドラー
2つの入力を受け取ります:
- 1つ目の温度値(整数)
- 2つ目の温度値(整数)
例えば、入力が25と30の場合、出力は以下のようになります:
Alert: Temperature is now 25 degrees
Logged: 25
Alert: Temperature is now 30 degrees
Logged: 30Temperatureプロパティを設定するだけで、すべてのサブスクライバーに自動的に通知されることに注目してください。モニターは誰がリッスンしているかを知る必要はありません。それがイベント駆動型アプローチの素晴らしい点です!
チートシート
デリゲートは、メソッドへの参照を表す型であり、メソッドを引数として渡したり、コレクションに格納したり、動的に呼び出したりすることができます。
メソッドシグネチャを指定してデリゲートを宣言します。
public delegate void Notify(string message);
public class Publisher
{
public void SendAlert(string msg) => Console.WriteLine("Alert: " + msg);
}
// 使用例
Notify notifier = new Publisher().SendAlert;
notifier("System started"); // Alert: System startedイベントは、デリゲートを使用してパブリッシュ/サブスクライブ(出版/購読)パターンを実装します。eventキーワードはデリゲートの使用を制限します。サブスクライバーはハンドラーの追加または削除のみが可能で、イベントを直接呼び出すことはできません。
public class Button
{
public event Action Clicked;
public void Click()
{
Clicked?.Invoke(); // すべてのサブスクライバーに通知
}
}
// サブスクライブ(購読)
Button btn = new Button();
btn.Clicked += () => Console.WriteLine("Button was clicked!");
btn.Click(); // Button was clicked!ハンドラーをサブスクライブするには+=を、解除するには-=を使用します。?.Invoke()パターンは、サブスクライバーが存在する場合にのみ安全にイベントを呼び出します。
自分で試してみよう
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// 入力を読み込む
int temp1 = Convert.ToInt32(Console.ReadLine());
int temp2 = Convert.ToInt32(Console.ReadLine());
// TODO: TemperatureMonitor インスタンスを作成する
// TODO: "Alert: Temperature is now {value} degrees" と出力するハンドラーを購読する
// TODO: "Logged: {value}" と出力する別のハンドラーを購読する
// TODO: 温度を temp1 に設定する
// TODO: 温度を temp2 に設定する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機8オブジェクト指向の高度な概念
継承よりコンポジションジェネリクス (クラスとメソッド)デリゲートとイベント属性とリフレクションIDisposable と using ステートメント依存性の注入 (DI) の基礎