C# デリゲート完全ガイド:イベント処理、コールバック、汎用デリゲート

C# デリゲート完全ガイド:イベント処理、コールバック、汎用デリゲート の詳細な説明

C# のデリゲートは、C# プログラミングにおける強力かつ不可欠な概念であり、関数ポインタと同様に、メソッドを引数として渡したり、メソッドを変数として扱ったりすることを可能にします。デリゲートを理解することは、イベント処理、コールバック、非同期プログラミング、そしてより抽象的なデザインパターンをマスターする上で不可欠です。

本ガイドでは、デリゲートの基本から高度な応用までを網羅的に解説し、具体的なコード例を交えながら、そのメカニズムと利用方法を深く掘り下げます。

目次

  1. デリゲートとは?
    • 1.1 デリゲートの定義と役割
    • 1.2 なぜデリゲートが必要なのか?
    • 1.3 デリゲートと関数ポインタの違い
  2. デリゲートの基本
    • 2.1 デリゲートの宣言
    • 2.2 デリゲートのインスタンス化
    • 2.3 デリゲートの呼び出し
    • 2.4 デリゲートのチェーン
    • 2.5 デリゲートと匿名メソッド
  3. 汎用デリゲート (Action, Func, Predicate)
    • 3.1 Action デリゲート
    • 3.2 Func デリゲート
    • 3.3 Predicate デリゲート
    • 3.4 汎用デリゲートの利点と活用例
  4. イベントとデリゲート
    • 4.1 イベントの概念と必要性
    • 4.2 イベントの宣言と発生
    • 4.3 イベントハンドラの登録と解除
    • 4.4 標準的なイベントパターン
    • 4.5 カスタムイベント引数
  5. コールバックとデリゲート
    • 5.1 コールバックの概念と用途
    • 5.2 非同期処理におけるコールバック
    • 5.3 UI イベント処理におけるコールバック
    • 5.4 例外処理とコールバック
  6. デリゲートとラムダ式
    • 6.1 ラムダ式の基本構文
    • 6.2 ラムダ式とデリゲートの組み合わせ
    • 6.3 クロージャ (Closure)
    • 6.4 LINQ とラムダ式
  7. デリゲートの応用例
    • 7.1 ソート処理におけるデリゲート
    • 7.2 フィルター処理におけるデリゲート
    • 7.3 プラグインアーキテクチャにおけるデリゲート
    • 7.4 デザインパターンにおけるデリゲート (Observer, Strategy)
  8. デリゲートの注意点とベストプラクティス
    • 8.1 デリゲートチェーンにおける例外処理
    • 8.2 メモリリークのリスクと対策
    • 8.3 デリゲートのnullチェック
    • 8.4 デリゲートの適切な使用場面
  9. まとめ

1. デリゲートとは?

1.1 デリゲートの定義と役割

デリゲートは、C# における型安全な関数ポインタのようなものです。つまり、特定のシグネチャ (戻り値の型と引数の型) を持つメソッドへの参照を保持できるオブジェクトです。デリゲートを使うことで、メソッド自体を引数として他のメソッドに渡したり、変数に格納したり、実行時に異なるメソッドを呼び出すことができます。

デリゲートは、以下のような役割を果たします。

  • メソッドのカプセル化: メソッドをオブジェクトとして扱うことで、コードの柔軟性と再利用性を高めます。
  • イベント処理: イベントが発生した際に、登録されたメソッド (イベントハンドラ) を呼び出す仕組みを提供します。
  • コールバック: 非同期処理やUIイベント処理において、特定の処理が完了した後に呼び出されるメソッドを指定できます。
  • デザインパターン: Observer パターンや Strategy パターンなど、様々なデザインパターンを実装するために利用されます。

1.2 なぜデリゲートが必要なのか?

C# は、基本的に静的な型付け言語であり、メソッドを直接変数に格納したり、引数として渡したりすることはできません。しかし、特定の状況下では、実行時にメソッドを柔軟に選択したり、異なる処理を適用したりする必要が生じます。

例えば、ソート処理を考えてみましょう。異なる基準でソートを行いたい場合、ソートアルゴリズム自体を変更するのではなく、比較を行うメソッドだけを切り替えることができれば、より柔軟な実装が可能になります。

デリゲートは、このような要求に応えるためのメカニズムを提供します。メソッドへの参照をデリゲートオブジェクトに格納することで、メソッドをデータとして扱い、実行時に動的に処理を切り替えることができます。

1.3 デリゲートと関数ポインタの違い

C++ などの言語で使用される関数ポインタは、メモリ上のアドレスを直接指し示すため、型安全ではありません。異なるシグネチャを持つメソッドを誤って呼び出してしまう可能性があり、実行時エラーを引き起こす可能性があります。

一方、C# のデリゲートは、型安全です。デリゲートを宣言する際に、対応するメソッドのシグネチャを定義する必要があり、異なるシグネチャを持つメソッドをデリゲートに割り当てようとすると、コンパイルエラーが発生します。

さらに、デリゲートは、複数のメソッドをチェーンとして保持できるマルチキャストデリゲートとしても機能します。これは、関数ポインタにはない機能です。

2. デリゲートの基本

2.1 デリゲートの宣言

デリゲートを宣言するには、delegate キーワードを使用します。デリゲートの宣言は、メソッドの宣言に似ていますが、実装は含みません。

“`csharp
// 戻り値の型: int, 引数: int, int のデリゲートを宣言
delegate int MathOperation(int x, int y);

// 戻り値の型: void, 引数: string のデリゲートを宣言
delegate void LogMessage(string message);
“`

MathOperation デリゲートは、int 型の引数を2つ受け取り、int 型の値を返すメソッドへの参照を保持できます。LogMessage デリゲートは、string 型の引数を1つ受け取り、何も返さない (void) メソッドへの参照を保持できます。

2.2 デリゲートのインスタンス化

デリゲートを宣言した後、そのデリゲート型のインスタンスを作成する必要があります。インスタンス化は、new キーワードを使用し、デリゲートに割り当てるメソッドの名前をコンストラクタに渡すことで行います。

“`csharp
// MathOperation デリゲートのインスタンスを作成
MathOperation add = new MathOperation(Add);
MathOperation subtract = new MathOperation(Subtract);

// LogMessage デリゲートのインスタンスを作成
LogMessage logger = new LogMessage(Console.WriteLine);

// デリゲートに対応するメソッドの例
static int Add(int x, int y) { return x + y; }
static int Subtract(int x, int y) { return x – y; }
“`

上記の例では、Add メソッドと Subtract メソッドへの参照を MathOperation デリゲートのインスタンスである addsubtract にそれぞれ割り当てています。logger デリゲートには、Console.WriteLine メソッドへの参照を割り当てています。

C# 2.0 以降では、より簡潔な記法でデリゲートのインスタンス化を行うことができます。

csharp
// より簡潔な記法
MathOperation add = Add;
LogMessage logger = Console.WriteLine;

2.3 デリゲートの呼び出し

デリゲートのインスタンスを作成した後、そのインスタンスを使用して、割り当てられたメソッドを呼び出すことができます。デリゲートの呼び出しは、通常のメソッド呼び出しと同じ構文で行います。

“`csharp
// デリゲートを呼び出す
int result1 = add(10, 5); // Add(10, 5) が呼び出される
int result2 = subtract(10, 5); // Subtract(10, 5) が呼び出される
logger(“Hello, world!”); // Console.WriteLine(“Hello, world!”) が呼び出される

Console.WriteLine($”Result1: {result1}”); // Result1: 15
Console.WriteLine($”Result2: {result2}”); // Result2: 5
“`

2.4 デリゲートのチェーン

デリゲートは、複数のメソッドをチェーンとして保持することができます。これをマルチキャストデリゲートと呼びます。マルチキャストデリゲートを使用すると、単一のデリゲートの呼び出しで、複数のメソッドを順番に実行することができます。

メソッドをチェーンに追加するには、+= 演算子を使用します。メソッドをチェーンから削除するには、-= 演算子を使用します。

“`csharp
// マルチキャストデリゲートの例
LogMessage logger = Console.WriteLine;
logger += LogToFile; // LogToFile メソッドをチェーンに追加

logger(“This message will be logged to both console and file.”);

logger -= Console.WriteLine; // Console.WriteLine メソッドをチェーンから削除

logger(“This message will be logged only to file.”);

static void LogToFile(string message)
{
// ファイルにログを書き込む処理
System.IO.File.AppendAllText(“log.txt”, message + Environment.NewLine);
}
“`

上記の例では、最初に Console.WriteLine メソッドへの参照を logger デリゲートに割り当てています。次に、LogToFile メソッドをチェーンに追加しています。logger デリゲートを呼び出すと、Console.WriteLineLogToFile が順番に実行されます。その後、Console.WriteLine をチェーンから削除すると、logger デリゲートを呼び出しても LogToFile だけが実行されるようになります。

重要: マルチキャストデリゲートが値を返す場合、最後にチェーンに追加されたメソッドの戻り値のみが返されます。チェーン内の他のメソッドの戻り値は破棄されます。例外処理に関しては、チェーン内のいずれかのメソッドが例外をスローした場合、その例外は呼び出し元に伝播し、チェーンの残りのメソッドは実行されません。

2.5 デリゲートと匿名メソッド

C# 2.0 では、匿名メソッドという機能が導入されました。匿名メソッドを使用すると、デリゲートのインスタンス化時に、メソッドを直接記述することができます。

“`csharp
// 匿名メソッドを使用したデリゲートのインスタンス化
MathOperation multiply = delegate (int x, int y)
{
return x * y;
};

int result = multiply(5, 3); // result = 15
“`

匿名メソッドは、名前のないメソッドであり、delegate キーワードを使用して定義されます。匿名メソッドは、通常のメソッドと同様に、引数を受け取り、処理を実行し、値を返すことができます。

匿名メソッドは、デリゲートを簡単に記述できるため、コードの簡潔さを向上させることができます。特に、一度しか使用しない短いメソッドを定義する場合に便利です。

3. 汎用デリゲート (Action, Func, Predicate)

C# には、頻繁に使用されるデリゲートの型を簡略化するために、汎用デリゲートが用意されています。代表的な汎用デリゲートとして、Action, Func, Predicate があります。

3.1 Action デリゲート

Action デリゲートは、値を返さないメソッド (void 型) を参照するために使用されます。Action デリゲートは、0個から16個までの引数を取ることができます。

“`csharp
// 引数なしの Action デリゲート
Action action1 = () => Console.WriteLine(“Action with no arguments”);
action1();

// 1つの引数を持つ Action デリゲート (string)
Action action2 = (message) => Console.WriteLine($”Action with message: {message}”);
action2(“Hello from Action”);

// 2つの引数を持つ Action デリゲート (int, int)
Action action3 = (x, y) => Console.WriteLine($”Action with two integers: {x}, {y}”);
action3(5, 10);
“`

3.2 Func デリゲート

Func デリゲートは、値を返すメソッドを参照するために使用されます。Func デリゲートは、0個から16個までの入力パラメータと1つの戻り値の型を指定できます。最後の型パラメータは戻り値の型を表します。

“`csharp
// 引数なしで int 型の値を返す Func デリゲート
Func func1 = () => 10;
int value1 = func1();
Console.WriteLine($”Func with no arguments: {value1}”);

// 1つの引数 (int) を受け取り、int 型の値を返す Func デリゲート
Func func2 = (x) => x * 2;
int value2 = func2(5);
Console.WriteLine($”Func with one argument: {value2}”);

// 2つの引数 (int, int) を受け取り、int 型の値を返す Func デリゲート
Func func3 = (x, y) => x + y;
int value3 = func3(5, 10);
Console.WriteLine($”Func with two arguments: {value3}”);
“`

3.3 Predicate デリゲート

Predicate デリゲートは、1つの引数を受け取り、bool 型の値を返すメソッドを参照するために使用されます。Predicate デリゲートは、特定の条件を満たすかどうかを判定するために使用されます。

“`csharp
// Predicate デリゲートの例
Predicate isEven = (x) => x % 2 == 0;
bool result1 = isEven(4); // true
bool result2 = isEven(7); // false

Console.WriteLine($”Is 4 even? {result1}”);
Console.WriteLine($”Is 7 even? {result2}”);
“`

3.4 汎用デリゲートの利点と活用例

汎用デリゲートを使用する利点は、次のとおりです。

  • コードの簡潔性: デリゲートの型を明示的に宣言する必要がないため、コードをより簡潔に記述できます。
  • 再利用性: 汎用デリゲートは、様々なメソッドに対応できるため、コードの再利用性を高めることができます。
  • 可読性: 汎用デリゲートを使用することで、コードの意図をより明確に伝えることができます。

汎用デリゲートは、LINQ (Language Integrated Query) やイベント処理など、様々な場面で活用されています。

4. イベントとデリゲート

4.1 イベントの概念と必要性

イベントは、オブジェクトの状態が変化したことや、特定の操作が完了したことなどを他のオブジェクトに通知するメカニズムです。イベントは、Observer パターンを実装するための基盤となります。

例えば、ボタンがクリックされたときに、そのことを通知する必要がある場合、イベントを使用します。ボタンオブジェクトは、Click イベントを発生させ、登録されたイベントハンドラを呼び出すことで、クリックされたことを通知します。

イベントを使用する利点は、次のとおりです。

  • 疎結合: イベントを使用することで、イベントを発生させるオブジェクトと、イベントを処理するオブジェクトの間の依存関係を減らすことができます。
  • 拡張性: イベントを使用することで、新しいイベントハンドラを簡単に追加することができます。
  • カプセル化: イベントを使用することで、イベントハンドラを直接呼び出すことを防ぎ、オブジェクトの状態を保護することができます。

4.2 イベントの宣言と発生

イベントを宣言するには、event キーワードを使用します。イベントは、デリゲート型に関連付けられます。

“`csharp
// イベントの宣言
public event EventHandler Click; // 標準的なEventHandlerデリゲートを使用

public event EventHandler MyEvent; // カスタムEventArgsを使用

// カスタムイベント引数の例
public class CustomEventArgs : EventArgs
{
public string Message { get; set; }
}

// イベントの発生
protected virtual void OnClick(EventArgs e)
{
Click?.Invoke(this, e); // null条件演算子を使用して、nullチェックを行う
}

protected virtual void OnMyEvent(CustomEventArgs e)
{
MyEvent?.Invoke(this, e);
}
“`

上記の例では、Click という名前のイベントを宣言しています。Click イベントは、EventHandler デリゲート型に関連付けられています。EventHandler デリゲートは、イベントを処理するための標準的なデリゲート型です。

イベントを発生させるには、通常、On[EventName] という名前の保護された仮想メソッドを作成し、その中でイベントを Invoke メソッドを使用して呼び出します。イベントが発生したことを通知するために、OnClick メソッドを呼び出すことができます。

4.3 イベントハンドラの登録と解除

イベントハンドラを登録するには、+= 演算子を使用します。イベントハンドラを解除するには、-= 演算子を使用します。

“`csharp
// イベントハンドラの登録
button.Click += Button_Click;

// イベントハンドラの解除
button.Click -= Button_Click;

// イベントハンドラの例
private void Button_Click(object sender, EventArgs e)
{
// ボタンがクリックされたときの処理
Console.WriteLine(“Button clicked!”);
}
“`

上記の例では、Button_Click メソッドを button オブジェクトの Click イベントに登録しています。ボタンがクリックされると、Button_Click メソッドが呼び出されます。

4.4 標準的なイベントパターン

C# では、イベントを処理するための標準的なパターンが定義されています。このパターンに従うことで、コードの一貫性を高め、保守性を向上させることができます。

  • イベントの名前: イベントの名前は、[Verb][Noun] の形式で命名されます。例えば、Click, ValueChanged, DataReceived などです。
  • イベント引数: イベント引数は、EventArgs クラスを継承したクラスで表されます。イベント引数には、イベントに関する情報が含まれます。
  • イベントハンドラ: イベントハンドラは、EventHandler デリゲート型または EventHandler<TEventArgs> デリゲート型で表されます。イベントハンドラは、イベントが発生したときに呼び出されるメソッドです。
  • イベント発生メソッド: イベント発生メソッドは、On[EventName] という名前の保護された仮想メソッドで定義されます。イベント発生メソッドは、イベントを発生させるためのメソッドです。

4.5 カスタムイベント引数

イベント引数は、イベントに関する情報を提供するために使用されます。標準的なイベント引数である EventArgs クラスは、イベントに関する情報を提供しません。イベントに関する情報を提供する必要がある場合は、EventArgs クラスを継承したカスタムイベント引数クラスを作成する必要があります。

“`csharp
// カスタムイベント引数の例
public class ValueChangedEventArgs : EventArgs
{
public int OldValue { get; set; }
public int NewValue { get; set; }
}

// イベントの宣言
public event EventHandler ValueChanged;

// イベントの発生
protected virtual void OnValueChanged(ValueChangedEventArgs e)
{
ValueChanged?.Invoke(this, e);
}

// イベントハンドラの例
private void Control_ValueChanged(object sender, ValueChangedEventArgs e)
{
Console.WriteLine($”Value changed from {e.OldValue} to {e.NewValue}”);
}
“`

上記の例では、ValueChangedEventArgs という名前のカスタムイベント引数クラスを定義しています。ValueChangedEventArgs クラスは、OldValue プロパティと NewValue プロパティを持ちます。これらのプロパティは、値が変更された前後の値を表します。

5. コールバックとデリゲート

5.1 コールバックの概念と用途

コールバックは、ある関数が別の関数に引数として渡され、後で呼び出される関数のことです。コールバックは、非同期処理やUIイベント処理など、様々な場面で使用されます。

例えば、ボタンがクリックされたときに、特定の処理を実行する必要がある場合、コールバックを使用します。ボタンオブジェクトは、クリックされたときに、登録されたコールバック関数を呼び出すことで、特定の処理を実行します。

コールバックを使用する利点は、次のとおりです。

  • 柔軟性: コールバックを使用することで、実行時に異なる処理を柔軟に選択することができます。
  • 非同期処理: コールバックを使用することで、非同期処理の結果を処理することができます。
  • UIイベント処理: コールバックを使用することで、UIイベントに応答することができます。

5.2 非同期処理におけるコールバック

非同期処理は、時間のかかる処理をバックグラウンドで実行し、その結果を後で処理する処理のことです。非同期処理は、UIの応答性を維持するために重要です。

非同期処理の結果を処理するには、コールバックを使用します。非同期処理を開始する関数に、コールバック関数を引数として渡します。非同期処理が完了すると、コールバック関数が呼び出され、結果が渡されます。

“`csharp
// 非同期処理の例
public void DoSomethingAsync(Action callback)
{
// 時間のかかる処理
Thread.Sleep(3000);
string result = “Async operation completed”;

// コールバック関数の呼び出し
callback(result);

}

// コールバック関数の例
private void MyCallback(string result)
{
Console.WriteLine($”Result from async operation: {result}”);
}

// 非同期処理の呼び出し
DoSomethingAsync(MyCallback);
“`

上記の例では、DoSomethingAsync メソッドは、時間のかかる処理をシミュレートしています。DoSomethingAsync メソッドは、Action<string> 型の引数を受け取ります。この引数は、コールバック関数を表します。

DoSomethingAsync メソッドは、非同期処理が完了した後、コールバック関数を呼び出し、結果を渡します。

5.3 UI イベント処理におけるコールバック

UI イベント処理では、ユーザーの操作 (ボタンのクリック、テキストボックスへの入力など) に応答するために、コールバックを使用します。

UI コントロールは、イベントを発生させます。イベントが発生すると、登録されたコールバック関数が呼び出されます。

“`csharp
// ボタンのクリックイベントハンドラの例
private void Button_Click(object sender, EventArgs e)
{
// ボタンがクリックされたときの処理
Console.WriteLine(“Button clicked!”);
}

// ボタンのクリックイベントへの登録
button.Click += Button_Click;
“`

上記の例では、Button_Click メソッドは、ボタンのクリックイベントハンドラです。ボタンがクリックされると、Button_Click メソッドが呼び出されます。

5.4 例外処理とコールバック

コールバック関数内で例外が発生した場合、その例外は呼び出し元に伝播します。例外を適切に処理するために、try-catch ブロックを使用する必要があります。

csharp
// コールバック関数での例外処理の例
private void MyCallback(string result)
{
try
{
// 何らかの処理
throw new Exception("Error in callback");
}
catch (Exception ex)
{
Console.WriteLine($"Exception in callback: {ex.Message}");
}
}

上記の例では、MyCallback メソッド内で例外が発生した場合、catch ブロックで例外を捕捉し、処理します。

6. デリゲートとラムダ式

6.1 ラムダ式の基本構文

ラムダ式は、匿名メソッドをより簡潔に記述するための記法です。ラムダ式は、=> 演算子を使用して、引数リストと式またはステートメントブロックを区切ります。

csharp
// ラムダ式の例
(x, y) => x + y // 2つの引数を受け取り、それらの合計を返すラムダ式
x => x * 2 // 1つの引数を受け取り、それを2倍にして返すラムダ式
() => Console.WriteLine("Hello") // 引数なしで "Hello" をコンソールに出力するラムダ式

ラムダ式は、型推論を利用することができます。つまり、コンパイラが引数の型を推論できる場合、引数の型を明示的に指定する必要はありません。

6.2 ラムダ式とデリゲートの組み合わせ

ラムダ式は、デリゲートのインスタンス化時に使用されることがよくあります。ラムダ式を使用することで、デリゲートをより簡潔に記述することができます。

“`csharp
// ラムダ式を使用したデリゲートのインスタンス化
Func add = (x, y) => x + y;

int result = add(5, 3); // result = 8
“`

上記の例では、Func<int, int, int> デリゲートのインスタンス化にラムダ式を使用しています。ラムダ式 (x, y) => x + y は、2つの引数を受け取り、それらの合計を返す関数を表します。

6.3 クロージャ (Closure)

クロージャとは、ラムダ式が定義されたスコープ内の変数をキャプチャし、その変数の値を保持する機能のことです。ラムダ式が実行される際に、キャプチャされた変数は、ラムダ式が定義されたときの値を保持します。

“`csharp
// クロージャの例
int factor = 2;
Func multiplyByFactor = x => x * factor;

factor = 3; // factor の値を変更

int result = multiplyByFactor(5); // result = 10 (factor の値は 2 のまま)
“`

上記の例では、ラムダ式 x => x * factor は、factor 変数をキャプチャしています。factor 変数の値を変更した後でも、multiplyByFactor デリゲートを呼び出すと、factor 変数の値は 2 のままです。

重要: ループ内でラムダ式を使用する際には、クロージャの動作に注意する必要があります。ループ変数をキャプチャするのではなく、ループ変数の値をローカル変数にコピーしてから、ラムダ式内でローカル変数をキャプチャするようにしてください。

6.4 LINQ とラムダ式

LINQ (Language Integrated Query) は、C# でコレクションを操作するための強力な機能です。LINQ では、ラムダ式を使用して、コレクションの要素をフィルタリングしたり、変換したりすることができます。

“`csharp
// LINQ とラムダ式の例
List numbers = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 偶数のみを抽出する
List evenNumbers = numbers.Where(x => x % 2 == 0).ToList();

// 各要素を2倍にする
List doubledNumbers = numbers.Select(x => x * 2).ToList();

Console.WriteLine(“Even numbers: ” + string.Join(“, “, evenNumbers)); // Even numbers: 2, 4, 6, 8, 10
Console.WriteLine(“Doubled numbers: ” + string.Join(“, “, doubledNumbers)); // Doubled numbers: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
“`

上記の例では、Where メソッドと Select メソッドを使用して、numbers リストの要素をフィルタリングしたり、変換したりしています。Where メソッドは、ラムダ式 x => x % 2 == 0 を使用して、偶数のみを抽出します。Select メソッドは、ラムダ式 x => x * 2 を使用して、各要素を2倍にします。

7. デリゲートの応用例

7.1 ソート処理におけるデリゲート

コレクションをソートする際に、比較ロジックをデリゲートとして渡すことで、様々なソート基準を柔軟に適用できます。

“`csharp
// ソート処理におけるデリゲートの例
List names = new List { “Charlie”, “Alice”, “Bob” };

// 長さでソートする
names.Sort((x, y) => x.Length.CompareTo(y.Length));

Console.WriteLine(string.Join(“, “, names)); // Alice, Bob, Charlie

// 逆順にソートする
names.Sort((x, y) => y.CompareTo(x));

Console.WriteLine(string.Join(“, “, names)); // Charlie, Bob, Alice
“`

7.2 フィルター処理におけるデリゲート

コレクションから特定の条件を満たす要素を抽出する際に、フィルター条件をデリゲートとして渡すことで、様々なフィルター処理を柔軟に適用できます。

“`csharp
// フィルター処理におけるデリゲートの例
List numbers = new List { 1, 2, 3, 4, 5, 6 };

// 3より大きい数のみを抽出する
List filteredNumbers = numbers.FindAll(x => x > 3);

Console.WriteLine(string.Join(“, “, filteredNumbers)); // 4, 5, 6
“`

7.3 プラグインアーキテクチャにおけるデリゲート

プラグインアーキテクチャでは、アプリケーションの機能を拡張するために、外部のモジュール (プラグイン) をロードします。デリゲートを使用することで、プラグインのメソッドを動的に呼び出すことができます。

“`csharp
// プラグインインターフェース
public interface IPlugin
{
string Name { get; }
string Execute(string input);
}

// プラグインマネージャー
public class PluginManager
{
private Dictionary> plugins = new Dictionary>();

public void RegisterPlugin(string name, Func<string, string> plugin)
{
    plugins[name] = plugin;
}

public string ExecutePlugin(string name, string input)
{
    if (plugins.ContainsKey(name))
    {
        return plugins[name](input);
    }
    else
    {
        return "Plugin not found";
    }
}

}
“`

7.4 デザインパターンにおけるデリゲート (Observer, Strategy)

  • Observer パターン: イベントとデリゲートを使用して、Observer パターンを実装できます。イベントを発生させるオブジェクト (Subject) は、イベントハンドラ (Observer) のリストを保持します。Subject の状態が変化すると、登録された Observer に通知します。
  • Strategy パターン: アルゴリズムや戦略をカプセル化し、実行時に切り替えるために、デリゲートを使用できます。デリゲートは、異なるアルゴリズムを指し示すことができ、クライアントは、どのアルゴリズムを使用するかを動的に選択できます。

8. デリゲートの注意点とベストプラクティス

8.1 デリゲートチェーンにおける例外処理

マルチキャストデリゲートにおいて、チェーン内のいずれかのメソッドが例外をスローした場合、その例外は呼び出し元に伝播し、チェーンの残りのメソッドは実行されません。すべてのメソッドが確実に実行されるようにするには、各メソッド内で例外をキャッチし、処理する必要があります。

“`csharp
// デリゲートチェーンにおける例外処理の例
Action action = () => {
try { / 処理 / } catch (Exception ex) { Console.WriteLine(ex.Message); }
};
action += () => {
try { / 処理 / } catch (Exception ex) { Console.WriteLine(ex.Message); }
};

action();
“`

8.2 メモリリークのリスクと対策

イベントハンドラを登録したオブジェクトが破棄される際に、イベントハンドラを解除しないと、メモリリークが発生する可能性があります。オブジェクトが破棄される際に、イベントハンドラを解除するか、WeakReference を使用するなどして、メモリリークを防ぐ必要があります。

8.3 デリゲートのnullチェック

デリゲートを呼び出す前に、デリゲートが null でないことを確認する必要があります。null 条件演算子 (?.) を使用すると、null チェックを簡潔に記述できます。

csharp
// デリゲートの null チェックの例
MyDelegate?.Invoke(argument);

8.4 デリゲートの適切な使用場面

デリゲートは、強力な機能ですが、すべての場面で使用するべきではありません。デリゲートを使用するべき場面は、以下のとおりです。

  • メソッドを引数として渡す必要がある場合
  • 実行時に異なるメソッドを呼び出す必要がある場合
  • イベント処理を行う必要がある場合

9. まとめ

本ガイドでは、C# のデリゲートについて、その基本から高度な応用までを網羅的に解説しました。デリゲートは、イベント処理、コールバック、非同期プログラミング、そしてより抽象的なデザインパターンをマスターする上で不可欠な概念です。

デリゲートを理解し、適切に活用することで、より柔軟で再利用性の高いコードを作成することができます。本ガイドが、読者の皆様の C# プログラミングスキル向上の一助となれば幸いです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール