C#デリゲートとは?初心者向けに使い方・仕組み・サンプルコードをわかりやすく解説

はじめに

C#のデリゲートは、メソッドを変数のように扱うための仕組みです。

デリゲートを理解すると、実行する処理を状況に応じて切り替えたり、別のメソッドへ処理を渡したりできるようになります。また、C#でよく使われるイベント、ラムダ式、LINQ、FuncActionなどを理解するうえでも欠かせない機能です。

一方で、初心者にとっては「メソッドを代入する」「処理そのものを引数として渡す」といった考え方がイメージしにくいかもしれません。

この記事では、C#デリゲートの意味や基本構文、使い方、内部の仕組みをサンプルコード付きでわかりやすく解説します。

1. C#デリゲートとは?初心者向けにわかりやすく解説

1-1. デリゲートは「メソッドを変数のように扱う仕組み」

C#のデリゲートとは、簡単にいうと、特定の形式を持つメソッドへの参照を保存できる型です。

通常の変数には、数値や文字列を代入します。

int number = 10;string message = "こんにちは";

これに対して、デリゲート型の変数にはメソッドを代入できます。

void SayHello(){Console.WriteLine("こんにちは");}

Action action = SayHello;action();

このコードでは、actionという変数にSayHelloメソッドを代入し、後からaction()で実行しています。

デリゲートを「メソッドを入れる箱」と考えると、初心者でもイメージしやすいでしょう。ただし、実際に格納されているのはメソッドの処理内容そのものではなく、どのメソッドを呼び出すかを示す参照情報です。

1-2. デリゲートを使うと何ができるのか

デリゲートを使うと、主に次のような処理ができます。

  • 実行するメソッドを条件によって切り替える

  • メソッドの引数として別のメソッドを渡す

  • 処理が終わった後に呼び出すコールバックを指定する

  • 複数のメソッドをまとめて実行する

  • ボタンのクリックなどのイベント処理を登録する

  • LINQの検索条件や並び替え条件を指定する

たとえば、計算方法をデリゲートとして切り替えることができます。

int Add(int x, int y){return x + y;}

int Subtract(int x, int y){return x - y;}

Func<int, int, int> operation;

operation = Add;Console.WriteLine(operation(10, 5)); // 15

operation = Subtract;Console.WriteLine(operation(10, 5)); // 5

同じoperationという変数を使いながら、実行する計算処理を差し替えています。

1-3. デリゲートが初心者にわかりにくい理由

デリゲートが初心者にわかりにくい理由の一つは、変数に値ではなくメソッドを代入するという考え方に慣れていないためです。

また、デリゲートを理解するには、次の知識も必要になります。

  • 引数

  • 戻り値

  • メソッドのシグネチャ

  • staticメソッドとインスタンスメソッド

  • null

  • ラムダ式

さらに、C#には自作デリゲートだけでなく、FuncActionPredicateなどの組み込みデリゲートもあります。そのため、最初からすべてを覚えようとすると混乱しやすくなります。

まずは、引数と戻り値が一致するメソッドをデリゲートに代入できるという基本から理解しましょう。

1-4. C#でデリゲートが使われる代表的な場面

C#では、デリゲートがさまざまな場所で利用されています。

代表的な場面は次のとおりです。

  • Windows FormsやWPFなどのイベント処理

  • ASP.NET Coreのミドルウェア

  • LINQの検索・変換・並び替え

  • 非同期処理のコールバック

  • 条件に応じた処理の切り替え

  • 共通処理へのカスタム処理の注入

  • タイマー実行時の処理指定

たとえば、LINQのWhereメソッドには、要素を残すかどうかを判定する処理を渡します。

int[] numbers = { 1, 2, 3, 4, 5 };

IEnumerable<int> evenNumbers = numbers.Where(number => number % 2 == 0);

foreach (int number in evenNumbers){Console.WriteLine(number);}

number => number % 2 == 0というラムダ式は、デリゲートに変換されてWhereへ渡されています。

2. C#デリゲートの基本構文と定義方法

2-1. delegateキーワードの基本構文

独自のデリゲート型を定義する場合は、delegateキーワードを使います。

基本構文は次のとおりです。

アクセス修飾子 delegate 戻り値の型 デリゲート型名(引数);

たとえば、引数と戻り値を持たないデリゲート型は次のように定義します。

public delegate void MessageHandler();

この宣言は、「引数がなく、戻り値がvoidであるメソッドを参照できるMessageHandler型」を定義しています。

デリゲート型の名前には、役割がわかる名称を付けましょう。イベントに関連するデリゲートでは、Handlerという接尾辞が使われることがあります。

2-2. デリゲート型を宣言する方法

次のコードでは、文字列を1つ受け取り、戻り値を返さないデリゲート型を定義しています。

public delegate void MessageHandler(string message);

このデリゲート型に代入できるのは、次の条件を満たすメソッドです。

  • 引数がstring型1つ

  • 戻り値がvoid

たとえば、次のメソッドを代入できます。

static void ShowMessage(string message){Console.WriteLine(message);}

一方、次のメソッドは引数の型が異なるため代入できません。

static void ShowNumber(int number){Console.WriteLine(number);}

2-3. デリゲートにメソッドを代入する方法

デリゲート型の変数を宣言し、対応するメソッドを代入します。

public delegate void MessageHandler(string message);

static void ShowMessage(string message){Console.WriteLine(message);}

MessageHandler handler = ShowMessage;

以前は、次のようにデリゲートのインスタンスを明示的に生成する書き方もよく使われていました。

MessageHandler handler = new MessageHandler(ShowMessage);

どちらも同じ意味ですが、通常は簡潔な次の書き方で問題ありません。

MessageHandler handler = ShowMessage;

メソッド名の後ろに()を付けない点に注意してください。

MessageHandler handler = ShowMessage;   // メソッドを代入ShowMessage("こんにちは");              // メソッドをその場で実行

2-4. デリゲートを呼び出す方法

デリゲートに代入したメソッドは、通常のメソッドと同じように呼び出せます。

handler("こんにちは");

次のようにInvokeメソッドを使うこともできます。

handler.Invoke("こんにちは");

どちらも同じメソッドを呼び出します。

デリゲートがnullになる可能性がある場合は、null条件演算子を使うと安全です。

handler?.Invoke("こんにちは");

handlernullなら、何も実行されません。

2-5. 戻り値あり・引数ありのデリゲートの書き方

2つの整数を受け取り、整数を返すデリゲート型は次のように定義できます。

public delegate int Calculation(int x, int y);

このデリゲートに対応するメソッドを用意します。

static int Add(int x, int y){return x + y;}

代入して呼び出すコードは次のとおりです。

Calculation calculation = Add;

int result = calculation(10, 20);

Console.WriteLine(result); // 30

デリゲートの引数と戻り値は、代入するメソッドと互換性がなければなりません。

3. C#デリゲートの使い方をサンプルコードで解説

3-1. 最もシンプルなデリゲートのサンプルコード

引数と戻り値を持たない、最もシンプルな例を確認しましょう。

using System;

public class Program{public delegate void SimpleDelegate();

public static void Main(){SimpleDelegate action = SayHello;action();}private static void SayHello(){Console.WriteLine("こんにちは");}

}

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

こんにちは

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

  1. SimpleDelegateというデリゲート型を定義する

  2. actionというデリゲート変数を作る

  3. SayHelloメソッドを代入する

  4. action()SayHelloを実行する

3-2. 引数を受け取るデリゲートのサンプルコード

次は、文字列を受け取るデリゲートです。

using System;

public class Program{public delegate void MessageDelegate(string message);

public static void Main(){MessageDelegate showMessage = PrintMessage;showMessage("C#のデリゲートを学習中です");}private static void PrintMessage(string message){Console.WriteLine(message);}

}

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

C#のデリゲートを学習中です

デリゲート経由で渡した引数は、参照先のPrintMessageメソッドに渡されます。

3-3. 戻り値を返すデリゲートのサンプルコード

次は、整数を2つ受け取り、計算結果を返す例です。

using System;

public class Program{public delegate int CalculationDelegate(int x, int y);

public static void Main(){CalculationDelegate calculation = Multiply;int result = calculation(4, 5);Console.WriteLine(result);}private static int Multiply(int x, int y){return x * y;}

}

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

20

デリゲートを呼び出した結果は、通常のメソッドと同じように変数へ代入できます。

3-4. 複数のメソッドを切り替えて実行するサンプルコード

デリゲートを使うと、条件に応じて処理を切り替えられます。

using System;

public class Program{public delegate int CalculationDelegate(int x, int y);

public static void Main(){string operationType = "subtract";CalculationDelegate calculation;if (operationType == "add"){calculation = Add;}else{calculation = Subtract;}int result = calculation(10, 3);Console.WriteLine(result);}private static int Add(int x, int y){return x + y;}private static int Subtract(int x, int y){return x - y;}

}

operationType"add"なら加算、それ以外なら減算を実行します。

呼び出し側は、実際にどのメソッドが代入されているかを意識せず、次の共通コードで処理できます。

int result = calculation(10, 3);

3-5. コールバック処理でデリゲートを使うサンプルコード

コールバックとは、ある処理の途中や完了後に呼び出してもらう処理のことです。

using System;

public class Program{public delegate void CompletionCallback(string result);

public static void Main(){ProcessData("サンプルデータ", ShowResult);}private static void ProcessData(string data,CompletionCallback callback){Console.WriteLine("データを処理しています");string result = data.ToUpperInvariant();callback(result);}private static void ShowResult(string result){Console.WriteLine($"処理結果: {result}");}

}

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

データを処理しています処理結果: サンプルデータ

ProcessDataメソッドはデータ処理を担当し、結果の表示方法は外部から渡されたcallbackに任せています。

このように、共通処理と個別処理を分離できるのがデリゲートのメリットです。

4. C#デリゲートの仕組みを初心者向けに解説

4-1. デリゲート型とメソッドのシグネチャの関係

メソッドのシグネチャとは、主にメソッドの引数の数、型、順番などによって決まる形式です。デリゲートへメソッドを代入するには、デリゲート型とメソッドの形式に互換性が必要です。

次のデリゲート型を例にします。

public delegate int Calculation(int x, int y);

このデリゲートには、次のメソッドを代入できます。

static int Add(int x, int y){return x + y;}

引数の型と順番、戻り値が一致しているためです。

一方、次のメソッドは代入できません。

static double Add(double x, double y){return x + y;}

デリゲートが要求するint型の引数と戻り値に一致していないためです。

4-2. デリゲートに代入できるメソッドの条件

基本的には、次の条件を確認します。

  • 引数の数が対応している

  • 引数の型と順番に互換性がある

  • 戻り値の型に互換性がある

  • ジェネリック型を使う場合は型引数が適切である

たとえば、次のデリゲート型を考えます。

public delegate bool Validator(string value);

代入できるメソッドは次のような形式です。

static bool IsNotEmpty(string value){return !string.IsNullOrEmpty(value);}

次のメソッドは戻り値がvoidなので代入できません。

static void PrintValue(string value){Console.WriteLine(value);}

コンパイラが型の不一致を検出するため、誤った組み合わせを実行前に発見できます。

4-3. デリゲートの内部では何が起きているのか

デリゲートは、参照先のメソッドに関する情報を保持するオブジェクトです。

インスタンスメソッドを参照する場合、概念的には次の情報を保持します。

  • 呼び出すメソッド

  • 呼び出し対象となるインスタンス

次のコードを見てみましょう。

public class Greeter{private readonly string _name;

public Greeter(string name){_name = name;}public void Greet(){Console.WriteLine($"こんにちは、{_name}さん");}

}

Greeter greeter = new Greeter("田中");Action action = greeter.Greet;

action();

actionにはGreetメソッドだけでなく、対象となるgreeterインスタンスとの組み合わせが保持されます。

一方、staticメソッドは特定のインスタンスに属さないため、対象インスタンスを用意せずに参照できます。

4-4. nullチェックと安全な呼び出し方

デリゲート変数は参照型なので、nullになる可能性があります。

Action? action = null;action();

このコードを実行すると、NullReferenceExceptionが発生します。

安全に呼び出すには、null条件演算子を使います。

action?.Invoke();

引数がある場合も同様です。

Action<string>? showMessage = null;showMessage?.Invoke("こんにちは");

通常のif文でも確認できます。

if (showMessage != null){showMessage("こんにちは");}

簡潔で意図が明確なため、多くの場合は?.Invoke()が使われます。

4-5. Invokeメソッドとは何か

Invokeは、デリゲートが参照しているメソッドを実行するためのメソッドです。

次の2つは、基本的に同じ意味です。

handler("こんにちは");
handler.Invoke("こんにちは");

Invokeを明示すると、変数がデリゲートであり、メソッドを呼び出していることがわかりやすくなります。

また、null条件演算子と組み合わせる場合は、次の書き方がよく使われます。

handler?.Invoke("こんにちは");

5. マルチキャストデリゲートとは?

5-1. 複数のメソッドをデリゲートに登録する方法

C#のデリゲートには、複数のメソッドを登録できます。これをマルチキャストデリゲートと呼びます。

using System;

public class Program{public delegate void Notification();

public static void Main(){Notification notification = SendEmail;notification += WriteLog;notification();}private static void SendEmail(){Console.WriteLine("メールを送信しました");}private static void WriteLog(){Console.WriteLine("ログを記録しました");}

}

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

メールを送信しましたログを記録しました

登録された複数のメソッドが順番に実行されます。

5-2. +=演算子でメソッドを追加する

デリゲートへメソッドを追加するには、+=演算子を使います。

Action action = First;action += Second;action += Third;

action()を実行すると、FirstSecondThirdの順番で呼び出されます。

最初の登録には、通常の代入演算子=を使えます。

Action action = First;

その後、+=で追加します。

action += Second;

最初から+=を使う場合は、変数を初期化しておく必要があります。

Action? action = null;action += First;action += Second;

5-3. -=演算子でメソッドを削除する

登録したメソッドを削除するには、-=演算子を使います。

Action action = First;action += Second;

action -= First;

action();

この場合、Secondだけが実行されます。

イベント処理では、不要になったハンドラーを解除するために-=が使われます。

button.Click += OnButtonClick;button.Click -= OnButtonClick;

特に、長期間動作するアプリケーションでは、不要なイベント購読を解除することが重要になる場合があります。

5-4. マルチキャストデリゲートの実行順序

複数のメソッドを登録した場合、基本的には登録された順番で実行されます。

Action action = First;action += Second;action += Third;

action();

実行順序は次のとおりです。

  1. First

  2. Second

  3. Third

ただし、途中のメソッドで例外が発生し、その例外を処理しなかった場合、それより後に登録されたメソッドは実行されません。

static void Second(){throw new InvalidOperationException("エラーが発生しました");}

各メソッドを個別に実行し、例外を個別に処理したい場合は、GetInvocationListを使用できます。

foreach (Delegate item in action.GetInvocationList()){try{item.DynamicInvoke();}catch (Exception exception){Console.WriteLine(exception.Message);}}

通常は型安全性を保つため、具体的なデリゲート型へキャストして呼び出す方法も検討します。

5-5. 戻り値がある場合の注意点

戻り値のあるマルチキャストデリゲートでは、複数のメソッドが実行されても、呼び出し式から取得できるのは最後に実行されたメソッドの戻り値です。

using System;

public class Program{public delegate int NumberProvider();

public static void Main(){NumberProvider provider = GetFirstNumber;provider += GetSecondNumber;int result = provider();Console.WriteLine(result);}private static int GetFirstNumber(){return 10;}private static int GetSecondNumber(){return 20;}

}

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

20

GetFirstNumberも実行されますが、変数resultに入るのは最後のGetSecondNumberが返した20です。

複数の結果をすべて利用したい場合は、戻り値ではなくコレクションへ追加する設計にするか、GetInvocationListで個別に呼び出す必要があります。

6. Func・Action・Predicateとデリゲートの違い

6-1. Funcとは何か

Funcは、戻り値があるメソッドを扱うための組み込みデリゲートです。

たとえば、2つの整数を受け取り、整数を返す処理は次のように表現できます。

Func<int, int, int> calculation = Add;

int result = calculation(10, 5);

Console.WriteLine(result);

Funcでは、最後の型引数が戻り値の型です。

Func<第1引数の型, 第2引数の型, 戻り値の型>

引数がなく、文字列を返す場合は次のように書きます。

Func<string> getMessage = () => "こんにちは";

6-2. Actionとは何か

Actionは、戻り値がないメソッドを扱うための組み込みデリゲートです。

引数がない場合は次のように書きます。

Action action = SayHello;action();

文字列を1つ受け取る場合は次のとおりです。

Action<string> showMessage = message =>{Console.WriteLine(message);};

showMessage("こんにちは");

Actionの戻り値は常にvoidです。

6-3. Predicateとは何か

Predicate<T>は、T型の値を1つ受け取り、bool値を返す組み込みデリゲートです。

定義のイメージは次のようになります。

delegate bool Predicate<T>(T value);

次の例では、数値が正の値かどうかを判定しています。

Predicate<int> isPositive = number => number > 0;

Console.WriteLine(isPositive(10)); // TrueConsole.WriteLine(isPositive(-5)); // False

List<T>.FindList<T>.FindAllなどで利用できます。

List<int> numbers = new() { 1, 2, 3, 4, 5 };

List<int> evenNumbers = numbers.FindAll(number => number % 2 == 0);

6-4. delegateを自作する場合とFunc/Actionを使う場合の違い

自作デリゲートとFuncActionは、どちらもメソッドを参照するデリゲート型です。

自作デリゲートには、用途を表す名前を付けられるメリットがあります。

public delegate bool UserValidator(User user);

この型名を見るだけで、ユーザーを検証する処理だと理解できます。

一方、Funcを使うと型定義を省略できます。

Func<User, bool> validator;

簡潔ですが、変数名や周囲のコードを見なければ用途が伝わりにくい場合があります。

イベント用の型として公開する場合や、業務上の意味を型名で明確にしたい場合は、自作デリゲートが役立ちます。メソッド内だけで使う単純な処理では、FuncActionが便利です。

6-5. 初心者はどれを使えばよいか

初心者は、次の基準で使い分けるとよいでしょう。

  • 戻り値がない処理にはAction

  • 戻り値がある処理にはFunc

  • 1つの値を判定する処理にはPredicate<T>

  • 意味のある専用型が必要な場合は自作デリゲート

  • イベントの仕組みを深く理解したい場合は自作デリゲートも学ぶ

実務ではFuncActionが頻繁に使われます。しかし、これらもデリゲートの一種なので、最初にdelegateキーワードによる定義方法を理解しておくと応用しやすくなります。

7. デリゲート・ラムダ式・イベントの関係

7-1. ラムダ式はデリゲートに代入できる

ラムダ式とは、メソッドを簡潔に記述するための構文です。

次の通常のメソッドを考えます。

static int Add(int x, int y){return x + y;}

この処理は、ラムダ式を使って次のように書けます。

Func<int, int, int> add = (x, y) => x + y;

複数行の処理も記述できます。

Action<string> showMessage = message =>{string formatted = $"メッセージ: {message}";Console.WriteLine(formatted);};

ラムダ式はデリゲート型へ変換され、デリゲート経由で呼び出されます。

7-2. 匿名メソッドとデリゲートの関係

匿名メソッドは、名前を付けずにその場で処理を定義する構文です。

Action<string> showMessage = delegate (string message){Console.WriteLine(message);};

ラムダ式を使うと、同じ処理をより簡潔に書けます。

Action<string> showMessage = message =>{Console.WriteLine(message);};

匿名メソッドは現在でも利用できますが、新しくコードを書く場合はラムダ式が使われることが一般的です。

ただし、古いC#コードを読むときには匿名メソッドが登場することがあるため、基本構文を知っておくと役立ちます。

7-3. eventとデリゲートの違い

eventはデリゲートを基盤とした機能ですが、デリゲートとまったく同じものではありません。

通常のデリゲートを外部へ公開すると、外部コードから代入や呼び出しができてしまう場合があります。

public Action? Changed;

一方、eventを付けると、外部コードからは基本的にイベントハンドラーの追加と削除だけができます。

public event Action? Changed;

クラスの外部では次の操作ができます。

publisher.Changed += OnChanged;publisher.Changed -= OnChanged;

しかし、通常は外部からイベントを直接発生させることはできません。

publisher.Changed(); // クラス外部からは呼び出せない

イベントを発生させる責任を発行元のクラスだけに限定できるため、安全な設計になります。

7-4. イベント処理でデリゲートが使われる理由

イベントでは、何かが発生したときに実行する処理を外部から登録する必要があります。

たとえば、ボタンを作るクラスは、ボタンがクリックされた後に何を実行するかを事前には知りません。そこで、クリック時に呼び出すメソッドをデリゲートとして登録できるようにします。

public class Button{public event Action? Clicked;

public void Click(){Console.WriteLine("ボタンがクリックされました");Clicked?.Invoke();}

}

利用側は処理を登録します。

Button button = new Button();

button.Clicked += () =>{Console.WriteLine("登録された処理を実行します");};

button.Click();

イベントの発行側と受信側を疎結合にできることが、デリゲートを使う大きな理由です。

7-5. LINQでデリゲートが使われる例

LINQの多くのメソッドは、引数としてデリゲートを受け取ります。

たとえば、Whereは各要素を残すかどうかを判断する処理を受け取ります。

List<int> numbers = new() { 1, 2, 3, 4, 5, 6 };

IEnumerable<int> evenNumbers = numbers.Where(number => number % 2 == 0);

Selectには、要素を別の形へ変換する処理を渡します。

IEnumerable<string> labels = numbers.Select(number => $"番号: {number}");

OrderByには、並び替えで使用する値を取得する処理を渡します。

List<string> names = new(){"Suzuki","Abe","Takahashi"};

IEnumerable<string> sortedNames = names.OrderBy(name => name.Length);

これらのラムダ式は、内部的には対応するデリゲート型として扱われます。

8. C#デリゲートの実践的な使いどころ

8-1. ボタン操作などのイベント処理

GUIアプリケーションでは、ボタンのクリック、文字入力、画面の読み込みなどに応じて処理を実行します。

Windows Formsでは、たとえば次のような形でクリックイベントを登録します。

button.Click += Button_Click;

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

ラムダ式を使うこともできます。

button.Click += (sender, e) =>{MessageBox.Show("ボタンがクリックされました");};

イベントハンドラーも、イベントに対応したデリゲートとして登録されています。

8-2. 条件によって処理を差し替える

デリゲートを使うと、大量のif文やswitch文を呼び出し側に書かず、処理を変数として切り替えられます。

Func<decimal, decimal> priceCalculator;

bool isMember = true;

if (isMember){priceCalculator = price => price * 0.9m;}else{priceCalculator = price => price;}

decimal finalPrice = priceCalculator(1000m);

Console.WriteLine(finalPrice);

会員なら10%引き、非会員なら通常価格を返します。

処理の選択と実行を分離できるため、コードを整理しやすくなります。

8-3. 共通処理にコールバックを渡す

共通処理の一部だけを呼び出し側で変更したい場合、コールバックを渡せます。

static void ExecuteWithLogging(Action operation){Console.WriteLine("処理を開始します");

try{operation();Console.WriteLine("処理が完了しました");}catch (Exception exception){Console.WriteLine($"エラー: {exception.Message}");}

}

利用側では、実行したい処理だけを渡します。

ExecuteWithLogging(() =>{Console.WriteLine("ファイルを処理しています");});

開始ログ、終了ログ、例外処理を共通化しながら、中身の処理を自由に差し替えられます。

8-4. 並び替えやフィルタリング処理

デリゲートは、並び替えやフィルタリングの条件を指定する場合にも便利です。

public class Product{public string Name { get; set; } = string.Empty;

public decimal Price { get; set; }

}

List<Product> products = new(){new Product { Name = "商品A", Price = 3000m },new Product { Name = "商品B", Price = 1000m },new Product { Name = "商品C", Price = 2000m }};

IEnumerable<Product> filteredProducts = products.Where(product => product.Price >= 2000m).OrderBy(product => product.Price);

Whereにはフィルタリング条件、OrderByには並び替えの基準を表すデリゲートが渡されています。

8-5. 拡張性の高いコードを書く

デリゲートを活用すると、共通機能を変更せずに、新しい処理を外部から追加できる設計を作れます。

public class DataProcessor{public void Process(IEnumerable<string> data,Action<string> handler){foreach (string item in data){handler(item);}}}

同じDataProcessorに、異なる処理を渡せます。

DataProcessor processor = new DataProcessor();

string[] data = { "A", "B", "C" };

processor.Process(data, item =>{Console.WriteLine(item);});

processor.Process(data, item =>{Console.WriteLine(item.ToLowerInvariant());});

DataProcessor自体を変更しなくても、表示、保存、変換などの処理を差し替えられます。

9. C#デリゲートで初心者がつまずきやすいエラーと対処法

9-1. メソッドの引数や戻り値が一致しない

デリゲート型とメソッドの形式が合わないと、コンパイルエラーになります。

public delegate int Calculation(int x, int y);

static double Add(double x, double y){return x + y;}

Calculation calculation = Add;

Calculationint型の引数と戻り値を要求していますが、Adddouble型を使用しているため代入できません。

次のように型を合わせます。

static int Add(int x, int y){return x + y;}

エラーが発生した場合は、引数の数、型、順番、戻り値を確認しましょう。

9-2. nullのデリゲートを呼び出してしまう

次のコードでは、デリゲートにメソッドが代入されていません。

Action? action = null;action();

このまま呼び出すと例外が発生します。

null条件演算子を使って安全に呼び出します。

action?.Invoke();

必ず処理が登録されているべき設計なら、初期値を設定する方法もあります。

Action action = () => { };

ただし、空の処理で初期化すると設定漏れに気づきにくくなる場合もあるため、用途に応じて選択してください。

9-3. staticメソッドとインスタンスメソッドの違いで混乱する

staticメソッドはクラスに属するため、インスタンスを作らずに代入できます。

Action action = Utility.ShowMessage;

インスタンスメソッドを代入する場合は、対象オブジェクトが必要です。

Greeter greeter = new Greeter();Action action = greeter.ShowMessage;

次のように、インスタンスメソッドをクラス名から直接参照することはできません。

Action action = Greeter.ShowMessage;

対象のメソッドがstaticかどうかを確認し、インスタンスメソッドならオブジェクトを作成しましょう。

9-4. マルチキャストデリゲートの戻り値で混乱する

戻り値のあるデリゲートへ複数のメソッドを登録した場合、呼び出し結果として取得できるのは最後のメソッドの戻り値です。

Func<int> provider = () => 10;provider += () => 20;

int result = provider();

Console.WriteLine(result); // 20

すべての戻り値が自動的に配列やリストになるわけではありません。

複数の結果が必要なら、次のように処理を個別に呼び出します。

foreach (Delegate item in provider.GetInvocationList()){var function = (Func<int>)item;int value = function();

Console.WriteLine(value);

}

9-5. FuncやActionとの使い分けがわからない

使い分けに迷ったときは、戻り値の有無を最初に確認しましょう。

戻り値がない場合はActionです。

Action<string> printer = message =>{Console.WriteLine(message);};

戻り値がある場合はFuncです。

Func<int, int, int> add = (x, y) => x + y;

真偽値を返す単純な判定処理では、Predicate<T>も利用できます。

Predicate<int> isEven = number => number % 2 == 0;

用途を型名で明確に表現したい場合は、独自のデリゲート型を定義します。

public delegate bool ProductValidator(Product product);

10. C#デリゲートに関するよくある質問

10-1. デリゲートは必ず使う必要がある?

すべてのC#プログラムで、独自のデリゲート型を定義する必要があるわけではありません。

単純なプログラムであれば、通常のメソッド呼び出しだけで十分です。しかし、イベント、LINQ、ラムダ式、FuncActionなどを使用すると、間接的にデリゲートを利用することになります。

処理を外部から渡したい場合や、実行する処理を差し替えたい場合にデリゲートが役立ちます。

10-2. デリゲートとインターフェースの違いは?

デリゲートは、主に1つの処理や一連の呼び出しを渡すために使います。

Action<string> logger;

インターフェースは、複数の関連する操作をまとめて契約として定義するときに使います。

public interface ILogger{void LogInformation(string message);

void LogError(string message);

}

単純なコールバックや一時的な処理の差し替えにはデリゲートが適しています。複数のメソッドや状態を持つオブジェクトとして振る舞いを抽象化したい場合は、インターフェースが適しています。

どちらが常に優れているというものではなく、表現したい責務の大きさによって使い分けます。

10-3. デリゲートとラムダ式はどちらを覚えるべき?

両方を覚える必要がありますが、先にデリゲートの基本を理解すると、ラムダ式の役割がわかりやすくなります。

デリゲートは、メソッドを参照する型や仕組みです。ラムダ式は、そのデリゲートへ代入する処理を簡潔に書くための構文です。

Func<int, int> square = number => number * number;

この例では、Func<int, int>がデリゲート型で、number => number * numberがラムダ式です。

両者は競合するものではなく、一緒に使われるものです。

10-4. デリゲートは古い書き方なのか?

デリゲートは古くて不要になった機能ではありません。

ラムダ式、イベント、LINQ、FuncActionなど、現在のC#で広く使われている機能もデリゲートを基盤としています。

ただし、単純な用途では独自のデリゲート型を定義せず、FuncActionを使うケースが増えています。

public delegate int Converter(string value);

このような独自型を作らず、次のように書くことができます。

Func<string, int> converter;

独自デリゲートが不要になったのではなく、用途に応じてより簡潔な組み込み型を選べるようになったと考えるとよいでしょう。

10-5. 初心者が次に学ぶべきC#の機能は?

デリゲートの基本を理解した後は、次の順番で学ぶと知識をつなげやすくなります。

  1. ラムダ式

  2. FuncAction

  3. イベント

  4. ジェネリック

  5. LINQ

  6. 非同期処理

  7. 例外処理

  8. インターフェースと依存性の注入

特に、ラムダ式とLINQを学ぶと、デリゲートが実際のC#コードでどのように活用されているかを理解できます。

まとめ

C#のデリゲートとは、メソッドを変数のように扱い、別の場所へ渡したり、後から呼び出したりするための仕組みです。

独自のデリゲート型は、delegateキーワードを使って定義します。

public delegate int Calculation(int x, int y);

対応するメソッドを代入して呼び出せます。

Calculation calculation = Add;int result = calculation(10, 20);

デリゲートを使う際は、参照するメソッドの引数や戻り値に互換性が必要です。また、nullになる可能性がある場合は、次のように安全に呼び出しましょう。

handler?.Invoke();

複数のメソッドを登録するマルチキャストデリゲート、組み込み型のFuncActionPredicateも、すべてデリゲートの仕組みを利用しています。

初心者は、まず次の3点を押さえることが重要です。

  • デリゲートはメソッドを参照する型である

  • 引数と戻り値が対応するメソッドを代入できる

  • デリゲートを通じて処理を呼び出したり、別のメソッドへ渡したりできる

この基本を理解すれば、ラムダ式、イベント、LINQなど、C#の実践的な機能もスムーズに学べるようになります。