ActionとFuncの違いは?
Action
とFunc
は、C#でよく使われるデリゲート(委譲)型ですが、目的に応じて使い分けます。
戻り値がないメソッドを参照するために使います。例えば、ボタンが押された時やイベントが発生した時に何かを実行したい場合に使用します。
Action myAction = () => Console.WriteLine("Action Executed");
myAction(); // コンソールに「Action Executed」が出力される
C#戻り値があるメソッドを参照します。Func
を使うと、入力を受け取り、結果を返す処理を簡潔に記述できます。最後の型パラメータが戻り値の型になります。
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5); // 8が返る
C#デリゲートは、メソッドの参照を持つ型です。Action
やFunc
もデリゲートをベースにしており、基本的にはデリゲートの一部として考えられます。
手動でデリゲートを定義することもできますが、Action
やFunc
はそれを簡略化するために用意されています。
カスタムデリゲートの例:
public delegate void CustomDelegate(string message);
CustomDelegate myDelegate = (msg) => Console.WriteLine(msg);
myDelegate("This is a custom delegate!");
C#しかし、Action
やFunc
を使えば、カスタムデリゲートを定義する必要がなく、簡単に同様の処理が可能です。
メソッドの登録
Action
やFunc
にメソッドを登録するときには、+=
演算子を使います。これは、イベントやデリゲートに対してメソッドを追加する際の基本的な操作です。なお、メソッドに引数が必要な場合は、ラムダ式を使用して引数を渡す必要があります。以下にAction
を例に説明します。
+= で直接メソッドを追加する場合
Action
に対してメソッドを登録する際、直接メソッドを参照できる場合は、+=
だけで問題ありません。このケースでは、メソッドのシグネチャがAction
と一致している必要があります(つまり、戻り値がなく、引数が同じであること)。
例:
public class GameManager {
public void DecreasePlayerLives() {
Console.WriteLine("Lives Decreased");
}
}
enemy.onEnemyReachedGoal += gameManager.DecreasePlayerLives;
C#ここでは、DecreasePlayerLives
は引数を取らないメソッドなので、そのままAction
に追加できます。
+= () => でラムダ式を使う場合
引数を持つメソッドや、複雑な処理を登録したい場合は、+=に加えてラムダ式を使う必要があります。ラムダ式を使うことで、Action
が呼び出された時に、その場でパラメータを指定したり、処理を動的に追加することができます。
例:
enemy.onEnemyReachedGoal += () => waveSpawner.OnEnemyDeactivated(enemyGameObject);
C#この場合、OnEnemyDeactivated
はenemyGameObject
という引数が必要なので、ラムダ式でAction
の中でこの引数を設定しています。
ラムダ式について
ラムダ式の基本
ラムダ式は、無名関数とも呼ばれ、関数名を持たずにその場で関数を定義して処理を行う方法です。簡潔に関数を記述でき、Action
やFunc
に直接登録できます。
(parameters) => { function body }
C#parameters
: 関数が受け取る引数(ない場合は()
)function body
: 実行されるコード
例:
Action greet = () => Console.WriteLine("Hello, World!");
greet();
C#引数ありの例:
Func<int, int, int> multiply = (x, y) => x * y;
Console.WriteLine(multiply(3, 4)); // 12
C#ActionとFuncの応用シナリオ
イベントハンドリング
例えば、プレイヤーが特定のエリアに到達したとき、ポイントを加算したり、次のレベルへ進めるための処理を追加したい場合、Action
を使ったイベントハンドリングが有効です。
// プレイヤーがエリアに到達したときのイベント
player.onPlayerReachedArea += () => gameManager.IncreaseScore();
C#この例では、プレイヤーが特定のエリアに入ったときにスコアを加算する処理が登録されています。Action
は動的に処理を追加できるので、他のエリアに到達した場合に違う処理を追加したり、複数の処理を同じイベントに紐付けることが簡単です。
複数の処理を追加する場合も、+=
で次々にイベントに処理を追加できます。
player.onPlayerReachedArea += () => gameManager.IncreaseScore();
player.onPlayerReachedArea += () => levelManager.UnlockNextLevel();
C#これにより、1つのイベントで複数の処理を同時に行うことができます。
コールバック関数
非同期処理や、ゲームの特定のタイミングで何かを行う必要がある場合にも、Action
やFunc
は便利です。たとえば、アイテム収集型のゲームにおいて、アイテムの収集が完了したら報酬を与える処理を実行するために、Action
をコールバックとして使えます。
public void CollectItems(Action onComplete)
{
// アイテムを収集する処理
Console.WriteLine("Collecting items...");
// アイテム収集が完了したらコールバックを実行
onComplete?.Invoke();
}
// アイテム収集が完了した時点で、報酬を与える処理
CollectItems(() => Console.WriteLine("Reward granted!"));
C#この例では、CollectItems
メソッドの完了後に、指定したコールバック関数が呼び出されます。コールバック関数を使用すると、非同期処理や順次処理が必要な場面で、処理が完了した後に動的に次の処理を追加できます。
補足: ActionとFuncの連携
特定の状況下では、Action
とFunc
を組み合わせることも非常に効果的です。例えば、一般的なRPGゲームのバトルシステムで、ターゲットの選定をFunc
で行い、その結果に基づいて攻撃を行うAction
を実行するパターンが考えられます。
// 敵が攻撃可能かどうかを判断するための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>
でダメージを与える処理を実行しています。このように、Func
とAction
を組み合わせることで、複雑な条件判定と処理を分離し、簡潔に管理できます。
最後に
2Dタワーディフェンスゲーム制作中にかなり重要そうな機能を知ったのでまとめてみました。ゲーム制作中にどんどん新しいことを知り、その度に内容をまとめているとなかなか時間が奪われますが、きっといつか自分の役に立つはず!
コメント