[C#] デリゲート型のActionとFunc

この記事の目的

この記事では、C#で使われるデリゲート型「Action」と「Func」 についてまとめます。
ラムダ式についてはここでは詳しく触れませんが、ActionやFuncと組み合わせて使うことが多いため、関連記事を参照してください。

ラムダ式の関連記事
ラムダ式 - ラムダ式と匿名関数 - C# reference
匿名関数と式形式のメンバーを作成するために使われる C# のラムダ式。

前提:「デリゲート」とは

C#では、「メソッドを変数のように扱う」ための仕組みとしてデリゲート(delegate) という概念があります。
C#の デリゲート(delegate) は、簡単に言うと 「メソッドを収納する箱」 のようなものです。

  • メソッドを変数のように 代入して保存できる
  • 必要なときに 箱から取り出して実行できる

たとえば、次のようなイメージです。

C#
// intを受け取って何かをする処理を表すデリゲート型を定義
public delegate void IntHandler(int value);

// メソッドをデリゲートに代入
IntHandler showValue = PrintValue;
showValue(5); // PrintValue(5) が呼ばれる

void PrintValue(int n)
{
    Console.WriteLine("Value: " + n);
}

このように、メソッドの参照を別の変数に持たせて、後から実行することができます。ただし、上のように都度 delegate を定義するのは少し面倒です。

そこで登場するのが、ActionFunc です。これらは、汎用的に使えるデリゲート型として .NET に標準で用意されています。

ActionとFuncの基本

種類戻り値用途
Actionなし処置を実行するだけ(イベント・通知など)
Funcあり入力を受けて結果を返す処理(計算・判定など)

Action:戻り値のない処理

  • 戻り値を持たないデリゲート型
  • 引数を0個〜16個まで指定可能
C#
Action sayHello = () => Console.WriteLine("Hello!");
sayHello();  // 出力: Hello!

Func:戻り値のある処理

  • 戻り値を持つデリゲート型
  • 最後の型パラメータが戻り値の型
C#
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5);  // 出力: 8

上記の例では、int 型を2つ受け取り、int 型を返す関数を表しています。

メソッドの登録

ActionFuncにメソッドを登録するときには、+=演算子を使います。これは、イベントやデリゲートに対してメソッドを追加する際の基本的な操作です。

なお、メソッドに引数が必要な場合は、ラムダ式を使用して引数を渡す必要があります。以下にActionを例に説明します。

+= で直接メソッドを追加する場合

Actionに対してメソッドを登録する際、直接メソッドを参照できる場合は、+=だけで問題ありません。この場合、メソッドのシグネチャ(引数・戻り値の型)が一致が Action , Func と一致している必要があります。

C#
public class GameManager {
    public void DecreasePlayerLives() {
        Console.WriteLine("Lives Decreased");
    }
}

enemy.onEnemyReachedGoal += gameManager.DecreasePlayerLives;
C#

ここでは、DecreasePlayerLivesは引数を取らないメソッドなので、そのままActionに追加できます。

+= () => でラムダ式を使う場合

引数を持つメソッドや、複雑な処理を登録したい場合は、+=に加えてラムダ式を使う必要があります。

C#
enemy.onEnemyReachedGoal += () =>
{
    waveSpawner.OnEnemyDeactivated(enemyGameObject);
    gameManager.UpdateScore(100);
};
C#

この場合、OnEnemyDeactivatedenemyGameObjectという引数が必要なので、ラムダ式でActionの中でこの引数を設定しています。

ActionとFuncの応用例

イベントハンドリング

例えば、プレイヤーが特定のエリアに到達したとき、ポイントを加算したり、次のレベルへ進めるための処理を追加したい場合、Actionを使ったイベントハンドリングが有効です。

C#
// プレイヤーがエリアに到達したときのイベント
player.onPlayerReachedArea += () => gameManager.IncreaseScore();
C#

この例では、プレイヤーが特定のエリアに入ったときにスコアを加算する処理が登録されています。Actionは動的に処理を追加できるので、他のエリアに到達した場合に違う処理を追加したり、複数の処理を同じイベントに紐付けることが簡単です。

複数の処理を追加する場合も、+=で次々にイベントに処理を追加できます。

C#
player.onPlayerReachedArea += () => gameManager.IncreaseScore();
player.onPlayerReachedArea += () => levelManager.UnlockNextLevel();
C#

これにより、1つのイベントで複数の処理を同時に行うことができます。

コールバック関数

非同期処理や、ゲームの特定のタイミングで何かを行う必要がある場合にも、ActionFuncは便利です。たとえば、アイテム収集型のゲームにおいて、アイテムの収集が完了したら報酬を与える処理を実行するために、Actionをコールバックとして使えます。

C#
public void CollectItems(Action onComplete)
{
    // アイテムを収集する処理
    Console.WriteLine("Collecting items...");

    // アイテム収集が完了したらコールバックを実行
    onComplete?.Invoke();
}

// アイテム収集が完了した時点で、報酬を与える処理
CollectItems(() => Console.WriteLine("Reward granted!"));
C#

この例では、CollectItemsメソッドの完了後に、指定したコールバック関数が呼び出されます。コールバック関数を使用すると、非同期処理や順次処理が必要な場面で、処理が完了した後に動的に次の処理を追加できます。

補足: ActionとFuncの連携

特定の状況下では、ActionFuncを組み合わせることも非常に効果的です。例えば、一般的なRPGゲームのバトルシステムで、ターゲットの選定をFuncで行い、その結果に基づいて攻撃を行うActionを実行するパターンが考えられます。

C#
// 敵が攻撃可能かどうかを判断するためのFunc
Func<Enemy, bool> canAttack = (enemy) => enemy.IsInAttackRange();

// 敵に攻撃を行うためのAction
Action<Enemy> attack = (enemy) => enemy.TakeDamage(10);

// 条件に基づいて攻撃を実行
if (canAttack(targetEnemy)) {
    attack(targetEnemy);
}
C#

この例では、Func<Enemy, bool>を使ってターゲットが攻撃範囲内にいるかどうかを判断し、攻撃可能な場合にAction<Enemy>でダメージを与える処理を実行しています。このように、FuncActionを組み合わせることで、複雑な条件判定と処理を分離し、簡潔に管理できます。

最後に

ActionとFuncは、C#のデリゲートをより簡潔に使うための基本要素です。
「戻り値が必要ならFunc、不要ならAction」 と覚えておけばOKです。

コメント

タイトルとURLをコピーしました