前回までは、WPF の XAML 構文について見てきました。
今回からは、C# のデリゲートを整理します。
デリゲートは、イベント、ラムダ式、LINQ、コールバックなど、C# のさまざまな機能の土台になる仕組みです。
最初は少し抽象的に見えますが、まずは「メソッドを変数のように扱うための型」と考えると理解しやすくなります。
デリゲート型とは
デリゲート型は、メソッドを参照するための型です。
たとえば、次のようなメソッドがあるとします。
static void SayHello()
{
Console.WriteLine("Hello");
}
通常は次のように直接呼び出します。
SayHello();
デリゲートを使うと、この SayHello メソッドを変数に入れて、あとから呼び出せます。
MessageHandler handler = SayHello;
handler();
つまりデリゲートは、次のような場面で使えます。
- あとで呼び出す処理を渡したい
- 処理の中身を外部から差し替えたい
- ある処理が終わったあとに呼ぶコールバックを渡したい
- 複数の通知先をまとめて呼び出したい
デリゲート型の定義方法
デリゲート型は、delegate キーワードで定義します。
delegate void MessageHandler();
これは、次の形のメソッドを参照できるデリゲート型です。
- 戻り値が
void - 引数がない
完全な例を見てみます。
using System;
delegate void MessageHandler();
class Program
{
static void Main()
{
MessageHandler handler = ShowMessage;
handler();
}
static void ShowMessage()
{
Console.WriteLine("メッセージを表示します");
}
}
実行結果は次のようになります。
メッセージを表示します
MessageHandler に代入できるのは、void を返し、引数を取らないメソッドです。
デリゲート型は、参照できるメソッドの形を決める型だと考えるとよいでしょう。
引数を持つデリゲート
引数を持つデリゲートも定義できます。
delegate void LogHandler(string message);
このデリゲートには、string を 1 つ受け取り、戻り値が void のメソッドを代入できます。
using System;
delegate void LogHandler(string message);
class Program
{
static void Main()
{
LogHandler logger = WriteLog;
logger("処理を開始しました");
}
static void WriteLog(string message)
{
Console.WriteLine($"LOG: {message}");
}
}
実行結果です。
LOG: 処理を開始しました
デリゲートを呼び出すと、実際には代入されている WriteLog メソッドが呼び出されます。
戻り値を持つデリゲート
戻り値を持つデリゲートも定義できます。
delegate int Calculator(int x, int y);
これは、int を 2 つ受け取り、int を返すメソッドを参照できます。
using System;
delegate int Calculator(int x, int y);
class Program
{
static void Main()
{
Calculator calculator = Add;
int result = calculator(10, 20);
Console.WriteLine(result);
}
static int Add(int x, int y)
{
return x + y;
}
}
実行結果です。
30
デリゲートに代入できるメソッドは、戻り値と引数の型が一致している必要があります。
最もシンプルなデリゲート例
最小限のデリゲート例は、次のようになります。
using System;
delegate void SimpleDelegate();
class Program
{
static void Main()
{
SimpleDelegate d = Hello;
d();
}
static void Hello()
{
Console.WriteLine("Hello Delegate");
}
}
このコードでは、次の行でデリゲート型を定義しています。
delegate void SimpleDelegate();
そして、次の行で Hello メソッドをデリゲート変数に代入しています。
SimpleDelegate d = Hello;
最後に、デリゲート経由でメソッドを呼び出しています。
d();
見た目はメソッド呼び出しに似ていますが、呼び出している相手は Hello ではなく、Hello を参照しているデリゲート変数です。
デリゲートオブジェクトを深掘りする
デリゲートは、単なる関数ポインターではありません。
C# のデリゲートはオブジェクトです。そのため、自分が参照しているメソッドの情報を持っています。
using System;
delegate void MessageHandler();
class Program
{
static void Main()
{
MessageHandler handler = ShowMessage;
Console.WriteLine(handler.Method.Name);
Console.WriteLine(handler.Target == null);
}
static void ShowMessage()
{
Console.WriteLine("Hello");
}
}
実行結果の例です。
ShowMessage
True
handler.Method には、参照しているメソッドの情報が入っています。
handler.Method.Name
で、メソッド名を取得できます。
handler.Target には、インスタンスメソッドの場合、そのメソッドを持つオブジェクトが入ります。
今回の ShowMessage は static メソッドなので、Target は null です。
インスタンスメソッドを参照する
インスタンスメソッドをデリゲートに代入すると、Target には対象のオブジェクトが入ります。
using System;
delegate void MessageHandler();
class Messenger
{
public void Show()
{
Console.WriteLine("インスタンスメソッドです");
}
}
class Program
{
static void Main()
{
Messenger messenger = new Messenger();
MessageHandler handler = messenger.Show;
handler();
Console.WriteLine(handler.Method.Name);
Console.WriteLine(handler.Target?.GetType().Name);
}
}
実行結果です。
インスタンスメソッドです
Show
Messenger
この場合、デリゲートは次の情報を持っています。
- どのオブジェクトを対象にするか
- どのメソッドを呼び出すか
そのため、デリゲートは「メソッドだけ」を持っているのではなく、「対象オブジェクトとメソッドの組み合わせ」を持つことがあります。
System.Delegate ベースクラス
C# のすべてのデリゲート型は、内部的には System.Delegate を基底に持ちます。
ただし、開発者が次のように直接継承してデリゲートを作ることはできません。
// これはできない
class MyDelegate : Delegate
{
}
デリゲート型は、必ず delegate キーワードで定義します。
delegate void MyDelegate();
System.Delegate は、デリゲートが共通して持つ基本機能を提供します。
代表的なものは次の通りです。
| メンバー | 内容 |
|---|---|
Method |
参照しているメソッド情報 |
Target |
インスタンスメソッドの対象オブジェクト |
DynamicInvoke |
実行時に引数を渡して呼び出す |
GetInvocationList |
呼び出し対象の一覧を取得する |
DynamicInvoke を使うと、実行時に引数を渡してデリゲートを呼び出せます。
using System;
delegate void MessageHandler(string message);
class Program
{
static void Main()
{
MessageHandler handler = Show;
Console.WriteLine(handler.Method.Name);
handler.DynamicInvoke("DynamicInvoke から呼び出し");
}
static void Show(string message)
{
Console.WriteLine(message);
}
}
実行結果です。
Show
DynamicInvoke から呼び出し
ただし、通常は handler("message") のように直接呼び出します。
DynamicInvoke は型安全性が弱く、通常の呼び出しよりも遅いため、リフレクション的な処理が必要な場面以外ではあまり使いません。
System.MulticastDelegate クラス
通常、C# で定義したデリゲート型は System.MulticastDelegate を継承します。
概念的な継承関係は次のようになります。
System.Object
└── System.Delegate
└── System.MulticastDelegate
└── 自分で定義したデリゲート型
MulticastDelegate の重要な特徴は、複数のメソッドを登録できることです。
using System;
delegate void NotifyHandler();
class Program
{
static void Main()
{
NotifyHandler handler = First;
handler += Second;
handler += Third;
handler();
}
static void First()
{
Console.WriteLine("First");
}
static void Second()
{
Console.WriteLine("Second");
}
static void Third()
{
Console.WriteLine("Third");
}
}
実行結果です。
First
Second
Third
ひとつのデリゲート変数を呼び出すだけで、登録されたメソッドが順番に実行されています。
この仕組みは、次回扱う「状態変更の通知」やイベントの基礎になります。
まとめ
デリゲート型は、メソッドを参照するための型です。
delegateキーワードでデリゲート型を定義する- 戻り値と引数が一致するメソッドを代入できる
- デリゲートはメソッドを参照するオブジェクトである
Methodで参照先メソッドを確認できるTargetでインスタンスメソッドの対象オブジェクトを確認できる- すべてのデリゲート型は
System.Delegateを基底に持つ - 通常のデリゲート型は
System.MulticastDelegateにより複数メソッドを登録できる
次回は、デリゲートを使ってオブジェクトの状態変化を通知する方法を見ていきます。