C#のActionとは?使い方・Funcとの違い・ラムダ式まで初心者向けに徹底解説
はじめに
C#を学んでいると、Actionという書き方を見かけることがあります。
C#Action action = () => Console.WriteLine("Hello");
action();
初めて見ると、「これはメソッドなの?」「delegateと何が違うの?」「Funcとはどう使い分けるの?」と疑問に感じる人も多いはずです。
Actionは、戻り値のない処理を変数のように扱える仕組みです。C#ではメソッドをそのまま変数に代入したり、別のメソッドの引数として渡したりできます。その代表的な型がActionです。
この記事では、C#のActionについて、基本的な使い方からFuncとの違い、ラムダ式、delegateとの関係、実践的な活用例まで初心者向けにわかりやすく解説します。
1. C#のActionとは?戻り値なしの処理を変数として扱えるデリゲート
Actionとは、C#に用意されている組み込みのデリゲート型です。
簡単に言うと、Actionは戻り値がない処理を入れておける型です。
たとえば、次のようなvoidメソッドがあります。
C#void SayHello()
{
Console.WriteLine("こんにちは");
}
このような戻り値のないメソッドを、Action型の変数に代入できます。
C#Action action = SayHello;
action();
このコードでは、SayHelloというメソッドをactionという変数に代入し、あとからaction()で実行しています。
つまりActionを使うと、処理そのものをデータのように扱えるようになります。
1-1. Actionは「voidメソッド」を代入できる組み込みデリゲート
Actionに代入できるのは、基本的に戻り値がvoidの処理です。
C#void PrintMessage()
{
Console.WriteLine("メッセージを表示します");
}
Action action = PrintMessage;
action();
このように、戻り値がないメソッドであればActionに代入できます。
一方、次のように戻り値があるメソッドはActionには代入できません。
C#int GetNumber()
{
return 10;
}
// エラー
// Action action = GetNumber;
Actionは「処理を実行するだけ」であり、結果を返すためのものではありません。戻り値が必要な場合は、後述するFuncを使います。
1-2. Actionを使うと何が便利になるのか
Actionを使うと、処理を柔軟に切り替えたり、別のメソッドに渡したりできます。
たとえば、処理の前後に共通処理を入れたい場合を考えてみましょう。
C#void Execute(Action action)
{
Console.WriteLine("処理を開始します");
action();
Console.WriteLine("処理を終了します");
}
このExecuteメソッドには、任意の処理を渡せます。
C#Execute(() => Console.WriteLine("データを保存します"));
Execute(() => Console.WriteLine("メールを送信します"));
実行結果は次のようになります。
処理を開始します
データを保存します
処理を終了します
処理を開始します
メールを送信します
処理を終了します
このように、Actionを使うと「共通の流れは同じだが、一部の処理だけ変えたい」という場面で便利です。
1-3. delegate・メソッド・ラムダ式との関係
Actionを理解するには、delegate、メソッド、ラムダ式の関係を整理するとわかりやすくなります。
delegateは、メソッドを参照するための型です。Actionは、そのdelegateの一種としてC#に最初から用意されています。
たとえば、次の3つはすべてActionに処理を代入する例です。
C#// 既存メソッドを代入
Action action1 = SayHello;
// 匿名メソッドを代入
Action action2 = delegate
{
Console.WriteLine("こんにちは");
};
// ラムダ式を代入
Action action3 = () =>
{
Console.WriteLine("こんにちは");
};
現在のC#では、ラムダ式を使ってActionを書くことが非常に多いです。
特に次のような短い書き方はよく使われます。
C#Action action = () => Console.WriteLine("Hello");
Actionは、メソッドやラムダ式を代入できる「処理の入れ物」と考えると理解しやすいです。
1-4. 初心者がActionでつまずきやすいポイント
初心者がActionでつまずきやすいポイントは、主に次のようなものです。
まず、Actionは戻り値を返せません。
C#Action action = () => 10; // エラー
戻り値が必要な場合はFunc<int>のように書きます。
次に、Actionは代入しただけでは実行されません。
C#Action action = () => Console.WriteLine("Hello");
// ここではまだ実行されない
実行するには、次のように呼び出す必要があります。
C#action();
また、Actionがnullの状態で実行すると例外が発生します。
C#Action action = null;
// NullReferenceException
// action();
安全に呼び出すには、次のように書きます。
C#action?.Invoke();
Actionは便利ですが、「戻り値がない」「呼び出さないと実行されない」「nullの可能性がある」という点を押さえておきましょう。
2. Actionの基本的な使い方
ここからは、Actionの基本的な使い方をコードで見ていきます。
まずは、引数なしのActionから理解するとわかりやすいです。
2-1. 引数なしのActionを定義して実行する
引数なしのActionは、次のように定義します。
C#Action action = () =>
{
Console.WriteLine("処理を実行しました");
};
action();
Actionに代入しているのは、ラムダ式です。
C#() =>
{
Console.WriteLine("処理を実行しました");
}
()は引数がないことを表します。=>の右側に実行したい処理を書きます。
処理が1行だけの場合は、次のように省略して書けます。
C#Action action = () => Console.WriteLine("処理を実行しました");
action();
Actionは、代入された処理をaction()で実行できます。
2-2. 既存メソッドをActionに代入する
Actionには、すでに定義されているメソッドも代入できます。
C#void ShowMessage()
{
Console.WriteLine("既存メソッドを実行しました");
}
Action action = ShowMessage;
action();
このとき、メソッド名の後ろに()は付けません。
C#Action action = ShowMessage; // 正しい
Action action = ShowMessage(); // 誤り
ShowMessageと書くと「メソッドそのもの」を渡す意味になります。
一方、ShowMessage()と書くと「メソッドを今すぐ実行する」という意味になります。
Actionに代入したい場合は、メソッドを実行するのではなく、メソッドそのものを渡す必要があります。
2-3. ActionをInvokeで呼び出す方法
Actionは、次のように呼び出せます。
C#action();
また、Invokeメソッドを使って呼び出すこともできます。
C#action.Invoke();
この2つはほぼ同じ意味です。
C#Action action = () => Console.WriteLine("Hello");
action();
action.Invoke();
どちらを使っても処理は実行されます。
普段は短く書けるaction()がよく使われます。ただし、nullチェックと組み合わせる場合はInvokeを使った書き方が便利です。
C#action?.Invoke();
この書き方は、actionがnullでなければ実行し、nullなら何もしません。
2-4. nullチェックして安全に実行する方法
Actionは参照型なので、nullになる可能性があります。
C#Action action = null;
この状態で実行すると、例外が発生します。
C#action(); // NullReferenceException
そのため、Actionを呼び出すときはnullチェックを行うと安全です。
C#if (action != null)
{
action();
}
より簡潔に書くなら、null条件演算子を使います。
C#action?.Invoke();
この書き方は、イベントやコールバック処理でよく使われます。
C#void Complete(Action onCompleted)
{
Console.WriteLine("処理中...");
onCompleted?.Invoke();
}
呼び出し側は、完了後に実行したい処理を渡せます。
C#Complete(() => Console.WriteLine("完了しました"));
何も渡さない設計にする場合でも、?.Invoke()を使えば安全に呼び出せます。
3. 引数ありのAction<T>・Action<T1,T2>の使い方
Actionには、引数を指定できるバージョンもあります。
引数が1つならAction<T>、引数が2つならAction<T1, T2>のように書きます。
C#Action<string> action1;
Action<string, int> action2;
Actionは戻り値なしの処理を表しますが、引数は受け取れます。
3-1. Action<T>で1つの引数を受け取る
Action<T>は、1つの引数を受け取る処理を表します。
C#Action<string> greet = name =>
{
Console.WriteLine($"{name}さん、こんにちは");
};
greet("田中");
実行結果は次のとおりです。
田中さん、こんにちは
Action<string>は、「string型の引数を1つ受け取り、戻り値は返さない処理」を意味します。
メソッドを代入することもできます。
C#void PrintName(string name)
{
Console.WriteLine($"名前: {name}");
}
Action<string> action = PrintName;
action("佐藤");
この場合、PrintNameメソッドの戻り値がvoidで、引数がstring型1つなので、Action<string>に代入できます。
3-2. Action<T1,T2>で複数の引数を受け取る
引数が2つある場合は、Action<T1, T2>を使います。
C#Action<string, int> printUser = (name, age) =>
{
Console.WriteLine($"{name}さんは{age}歳です");
};
printUser("山田", 25);
実行結果は次のようになります。
山田さんは25歳です
Action<string, int>は、「string型とint型の引数を受け取り、戻り値は返さない処理」を意味します。
既存メソッドを代入する場合も同じです。
C#void ShowUser(string name, int age)
{
Console.WriteLine($"{name}: {age}");
}
Action<string, int> action = ShowUser;
action("鈴木", 30);
引数の数と型が一致していれば、Action<T1, T2>に代入できます。
3-3. Actionは最大何個まで引数を指定できるのか
C#のActionには、引数なしのActionから、複数引数を受け取るAction<T1, T2, ...>まで用意されています。
一般的な.NETでは、Action<T1, T2, ..., T16>のように、最大16個の引数まで指定できます。
C#Action<int, int, int> action = (a, b, c) =>
{
Console.WriteLine(a + b + c);
};
action(1, 2, 3);
ただし、引数が多すぎるActionは可読性が下がります。
たとえば、次のようなコードは読みにくくなりがちです。
C#Action<string, int, bool, DateTime, decimal> action =
(name, age, isActive, createdAt, price) =>
{
// 処理
};
引数が多くなる場合は、専用のクラスやレコードにまとめることを検討しましょう。
C#class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsActive { get; set; }
}
そして、次のように書くと読みやすくなります。
C#Action<UserInfo> action = user =>
{
Console.WriteLine(user.Name);
};
3-4. 引数の型が合わないときに起こるエラー
Actionに代入するメソッドやラムダ式は、引数の型と数が一致している必要があります。
たとえば、次のメソッドがあるとします。
C#void PrintNumber(int number)
{
Console.WriteLine(number);
}
これはAction<int>には代入できます。
C#Action<int> action = PrintNumber;
しかし、Action<string>には代入できません。
C#// エラー
// Action<string> action = PrintNumber;
Action<string>はstring型の引数を受け取る処理を求めていますが、PrintNumberはint型を受け取るため型が合いません。
ラムダ式でも同じです。
C#Action<int> action = number =>
{
Console.WriteLine(number);
};
action(10); // OK
// action("10"); // エラー
C#は型に厳しい言語なので、Actionを使うときも引数の型を正しく合わせる必要があります。
4. ActionとFuncの違い
Actionとよく一緒に出てくるのがFuncです。
どちらもメソッドやラムダ式を変数のように扱うためのデリゲートですが、最大の違いは戻り値があるかどうかです。
4-1. Actionは戻り値なし、Funcは戻り値あり
Actionは戻り値がありません。
C#Action action = () =>
{
Console.WriteLine("Hello");
};
action();
一方、Funcは戻り値があります。
C#Func<int> func = () =>
{
return 10;
};
int result = func();
Console.WriteLine(result);
Func<int>は、「引数なしで、int型の値を返す処理」を表します。
引数がある場合は、最後の型が戻り値の型になります。
C#Func<int, int, int> add = (a, b) =>
{
return a + b;
};
int result = add(3, 5);
Console.WriteLine(result);
この場合、Func<int, int, int>の意味は次のとおりです。
第1引数: int
第2引数: int
戻り値: int
Actionは戻り値なし、Funcは戻り値ありと覚えるとよいでしょう。
4-2. ActionとFuncの使い分け早見表
ActionとFuncの違いを整理すると、次のようになります。
| 種類 | 戻り値 | 例 | 用途 |
|---|---|---|---|
Action | なし | Action<string> | 表示、保存、通知など実行するだけの処理 |
Func | あり | Func<int, int> | 計算、変換、取得など結果を返す処理 |
Predicate<T> | bool | Predicate<int> | 条件判定 |
たとえば、ログを出力するだけならActionが向いています。
C#Action<string> log = message =>
{
Console.WriteLine(message);
};
一方、文字列を加工して返すならFuncが向いています。
C#Func<string, string> toUpper = text =>
{
return text.ToUpper();
};
条件判定を行う場合は、Predicate<T>やFunc<T, bool>が使われます。
C#Predicate<int> isEven = number => number % 2 == 0;
4-3. Actionを使うべきケース
Actionを使うべきなのは、戻り値を必要としない処理です。
代表的な例は次のようなものです。
C#Action save = () =>
{
Console.WriteLine("保存しました");
};
Action<string> log = message =>
{
Console.WriteLine($"ログ: {message}");
};
Action onCompleted = () =>
{
Console.WriteLine("完了しました");
};
Actionが向いている処理には、次のような特徴があります。
| 処理内容 | Actionが向いている理由 |
|---|---|
| ログ出力 | 結果を返す必要がない |
| 画面表示 | 表示するだけでよい |
| 保存処理 | 成功・失敗を別の方法で扱う場合 |
| 通知処理 | 実行できればよい |
| コールバック | 完了後に処理を呼びたい |
たとえば、処理完了後にメッセージを表示したい場合はActionが自然です。
C#void Download(Action onCompleted)
{
Console.WriteLine("ダウンロード中...");
onCompleted?.Invoke();
}
Download(() => Console.WriteLine("ダウンロード完了"));
4-4. Funcを使うべきケース
Funcを使うべきなのは、処理の結果を受け取りたい場合です。
たとえば、数値を計算して返す処理です。
C#Func<int, int, int> add = (a, b) =>
{
return a + b;
};
int result = add(10, 20);
Console.WriteLine(result);
文字列を変換して返す場合にもFuncを使います。
C#Func<string, string> format = text =>
{
return $"[{text}]";
};
string result = format("Hello");
Console.WriteLine(result);
また、条件に応じて値を返したい場合にもFuncが使えます。
C#Func<int, string> judge = score =>
{
return score >= 60 ? "合格" : "不合格";
};
処理結果を次の処理で使いたい場合は、ActionではなくFuncを選びましょう。
4-5. Predicateとの違いもあわせて理解する
Predicate<T>は、T型の引数を受け取り、boolを返すデリゲートです。
C#Predicate<int> isPositive = number =>
{
return number > 0;
};
Console.WriteLine(isPositive(10)); // True
Console.WriteLine(isPositive(-5)); // False
これは、次のFunc<T, bool>と似ています。
C#Func<int, bool> isPositive = number =>
{
return number > 0;
};
Predicate<T>は、条件判定を表すことが明確になる点がメリットです。
Action、Func、Predicateを比較すると、次のようになります。
| 型 | 意味 | 例 |
|---|---|---|
Action<T> | 引数を受け取り、戻り値なし | Action<string> |
Func<T, TResult> | 引数を受け取り、結果を返す | Func<int, string> |
Predicate<T> | 引数を受け取り、真偽値を返す | Predicate<int> |
戻り値がないならAction、戻り値があるならFunc、条件判定ならPredicateと考えると整理しやすいです。
5. Actionとラムダ式の関係
Actionはラムダ式と組み合わせて使われることが非常に多いです。
ラムダ式を使うと、短い処理をその場で簡潔に書けます。
5-1. ラムダ式をActionに代入する基本構文
ラムダ式をActionに代入する基本構文は次のとおりです。
C#Action action = () =>
{
// 実行したい処理
};
引数がある場合は、次のように書きます。
C#Action<string> action = message =>
{
Console.WriteLine(message);
};
引数が複数ある場合は、丸かっこで囲みます。
C#Action<string, int> action = (name, age) =>
{
Console.WriteLine($"{name}: {age}");
};
ラムダ式は、メソッドを別に定義しなくてもその場で処理を書けるため、短いコールバック処理などに向いています。
5-2. 引数なしラムダ式をActionで使う
引数なしのラムダ式は、()を使って書きます。
C#Action action = () =>
{
Console.WriteLine("引数なしのラムダ式です");
};
action();
処理が1行だけなら、次のように書けます。
C#Action action = () => Console.WriteLine("Hello");
action();
引数なしのActionは、ボタンクリック時の処理、完了時の処理、共通処理の差し替えなどでよく使われます。
C#void Run(Action action)
{
action();
}
Run(() => Console.WriteLine("実行されました"));
このように、Actionを引数にすることで、呼び出し側が実行したい処理を自由に指定できます。
5-3. 引数ありラムダ式をAction<T>で使う
引数ありのラムダ式は、Action<T>と組み合わせます。
C#Action<string> print = message =>
{
Console.WriteLine(message);
};
print("こんにちは");
引数が1つの場合、引数名の丸かっこは省略できます。
C#Action<string> print = message => Console.WriteLine(message);
ただし、型を明示したい場合は丸かっこが必要です。
C#Action<string> print = (string message) =>
{
Console.WriteLine(message);
};
複数の引数を使う場合は、丸かっこが必要です。
C#Action<string, int> show = (name, age) =>
{
Console.WriteLine($"{name}さんは{age}歳です");
};
show("田中", 20);
Action<T>を使うと、データを受け取って何かを実行する処理を簡潔に表現できます。
5-4. 複数行の処理をラムダ式で書く方法
ラムダ式の処理が複数行になる場合は、波かっこ{}を使います。
C#Action<string> process = message =>
{
Console.WriteLine("処理を開始します");
Console.WriteLine($"メッセージ: {message}");
Console.WriteLine("処理を終了します");
};
process("Hello");
複数行のラムダ式では、通常のメソッドと同じように変数を定義できます。
C#Action<int, int> calculate = (a, b) =>
{
int sum = a + b;
Console.WriteLine($"合計: {sum}");
};
calculate(3, 7);
ただし、処理が長くなりすぎる場合は、ラムダ式ではなくメソッドとして切り出したほうが読みやすくなります。
C#void CalculateAndPrint(int a, int b)
{
int sum = a + b;
Console.WriteLine($"合計: {sum}");
}
Action<int, int> action = CalculateAndPrint;
ラムダ式は便利ですが、何でもラムダ式で書けばよいわけではありません。短く明確な処理に使うのがおすすめです。
5-5. ラムダ式でActionを書くときの注意点
ラムダ式でActionを書くときは、戻り値を書かないように注意しましょう。
C#// エラー
// Action action = () => return 10;
Actionは戻り値なしの処理なので、値を返すことはできません。
また、1行ラムダ式では次のような戻り値のある式はActionに代入できない場合があります。
C#// エラーになりやすい例
// Action action = () => 10;
値を返したいならFuncを使います。
C#Func<int> func = () => 10;
また、ラムダ式では外側の変数を参照できます。
C#int count = 0;
Action action = () =>
{
count++;
Console.WriteLine(count);
};
action();
action();
このように外側の変数を使うことをキャプチャと呼びます。便利ですが、意図しないタイミングで値が変わることもあるため注意が必要です。
6. Actionの実践的な活用例
ここからは、実際の開発でよく使われるActionの活用例を紹介します。
Actionは単なる文法知識ではなく、コールバック、イベント的な処理、リスト処理、共通処理の切り替えなどでよく登場します。
6-1. コールバック処理としてActionを渡す
Actionの代表的な使い方が、コールバック処理です。
コールバックとは、「ある処理が終わったあとに呼び出される処理」のことです。
C#void LoadData(Action onCompleted)
{
Console.WriteLine("データを読み込み中...");
// 読み込み完了後に実行
onCompleted?.Invoke();
}
呼び出し側は、完了後に実行したい処理を渡します。
C#LoadData(() =>
{
Console.WriteLine("読み込みが完了しました");
});
このようにすると、LoadDataメソッドの中身を変更しなくても、完了後の処理を自由に差し替えられます。
エラー時の処理も渡したい場合は、複数のActionを使うこともあります。
C#void LoadData(Action onSuccess, Action onError)
{
bool success = true;
if (success)
{
onSuccess?.Invoke();
}
else
{
onError?.Invoke();
}
}
呼び出し側は次のように書けます。
C#LoadData(
() => Console.WriteLine("成功しました"),
() => Console.WriteLine("失敗しました")
);
6-2. 処理完了後のイベント的な使い方
Actionは、イベントのような使い方もできます。
C#class Downloader
{
public Action OnCompleted;
public void Start()
{
Console.WriteLine("ダウンロード開始");
// 処理完了
OnCompleted?.Invoke();
}
}
使う側は、完了時の処理を登録します。
C#Downloader downloader = new Downloader();
downloader.OnCompleted = () =>
{
Console.WriteLine("ダウンロードが完了しました");
};
downloader.Start();
ただし、本格的なイベントとして使う場合は、C#のeventキーワードを使うほうが適していることもあります。
C#class Downloader
{
public event Action OnCompleted;
public void Start()
{
Console.WriteLine("ダウンロード開始");
OnCompleted?.Invoke();
}
}
eventを付けると、外部から直接OnCompleted = nullのように上書きされることを防げます。
簡単なコールバックならAction、イベントとして公開するならevent Actionを検討するとよいでしょう。
6-3. List.ForEachでActionを使う
List<T>には、各要素に対して処理を実行するForEachメソッドがあります。
このForEachの引数にはAction<T>を渡します。
C#List<string> names = new List<string>
{
"田中",
"佐藤",
"鈴木"
};
names.ForEach(name =>
{
Console.WriteLine(name);
});
これは、リストの各要素を順番に取り出して、ラムダ式の処理を実行しています。
1行で書くこともできます。
C#names.ForEach(name => Console.WriteLine(name));
既存メソッドを渡すこともできます。
C#void PrintName(string name)
{
Console.WriteLine(name);
}
names.ForEach(PrintName);
List.ForEachを理解すると、Action<T>が「各要素に対して行う処理」を表していることがよくわかります。
6-4. 共通処理をActionで切り替える
Actionは、共通処理の一部だけを差し替えたいときにも便利です。
たとえば、ログ付きで処理を実行するメソッドを作ります。
C#void ExecuteWithLog(string title, Action action)
{
Console.WriteLine($"{title}を開始します");
action();
Console.WriteLine($"{title}を終了します");
}
呼び出し側は、実行したい処理だけを渡します。
C#ExecuteWithLog("保存処理", () =>
{
Console.WriteLine("データを保存しました");
});
ExecuteWithLog("削除処理", () =>
{
Console.WriteLine("データを削除しました");
});
このようにすると、開始ログと終了ログの処理を毎回書かずに済みます。
例外処理も共通化できます。
C#void SafeExecute(Action action)
{
try
{
action();
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
}
}
使う側は次のように書けます。
C#SafeExecute(() =>
{
Console.WriteLine("処理を実行します");
});
共通処理の中に、差し替え可能な処理を埋め込みたいときにActionは役立ちます。
6-5. Unityや非同期処理でよく見るActionの使い方
UnityでもActionはよく使われます。
たとえば、ゲーム内で「処理が完了したら次の処理を実行する」という場面です。
C#using System;
public class Player
{
public Action OnDead;
public void Damage(int amount)
{
Console.WriteLine($"{amount}ダメージを受けました");
// HPが0になったと仮定
OnDead?.Invoke();
}
}
使う側は、プレイヤーが倒れたときの処理を登録できます。
C#Player player = new Player();
player.OnDead = () =>
{
Console.WriteLine("ゲームオーバー処理を実行します");
};
player.Damage(100);
また、非同期処理でもActionは見かけます。
C#void LoadAsync(Action onCompleted)
{
Console.WriteLine("非同期処理を開始");
// 処理完了後
onCompleted?.Invoke();
}
ただし、asyncな処理を扱う場合は注意が必要です。
C#Action action = async () =>
{
await Task.Delay(1000);
Console.WriteLine("完了");
};
このようにasyncラムダ式をActionに代入すると、async voidのような扱いになります。例外処理が難しくなるため、非同期処理ではFunc<Task>を使うほうが安全です。
C#Func<Task> action = async () =>
{
await Task.Delay(1000);
Console.WriteLine("完了");
};
await action();
非同期処理では、戻り値がなくてもActionではなくFunc<Task>を検討しましょう。
7. Actionとdelegateの違い
Actionはdelegateの一種です。
そのため、「Actionとdelegateはまったく別物」というより、Actionはあらかじめ用意された便利なdelegate型と考えるとわかりやすいです。
7-1. delegateを自作する方法
C#では、delegateを自分で定義できます。
C#delegate void MyAction();
これは、引数なし・戻り値なしのメソッドを参照できるデリゲート型です。
使い方は次のようになります。
C#delegate void MyAction();
void SayHello()
{
Console.WriteLine("Hello");
}
MyAction action = SayHello;
action();
引数ありのdelegateも作れます。
C#delegate void MessageAction(string message);
void PrintMessage(string message)
{
Console.WriteLine(message);
}
MessageAction action = PrintMessage;
action("こんにちは");
このように、delegateを使えば独自の型名を持つデリゲートを定義できます。
7-2. Actionを使うとdelegate定義を省略できる
引数なし・戻り値なしのdelegateは、Actionで代用できます。
C#delegate void MyAction();
これは、次のActionとほぼ同じように使えます。
C#Action action;
引数がある場合も同じです。
C#delegate void MessageAction(string message);
これは、次のように書けます。
C#Action<string> action;
つまり、Actionを使えば自分でdelegateを定義しなくても、よくある戻り値なしの処理を表現できます。
C#Action<string> print = message =>
{
Console.WriteLine(message);
};
print("Hello");
シンプルな処理であれば、自作delegateよりActionを使うほうが簡潔です。
7-3. 自作delegateを使うべきケース
自作delegateを使うべきなのは、処理の意味を型名で明確にしたい場合です。
たとえば、次のようなAction<string>があるとします。
C#Action<string> action;
これだけでは、stringが何を意味するのか少しわかりにくいです。
一方、自作delegateを使うと意味が明確になります。
C#delegate void ErrorHandler(string errorMessage);
このように書くと、「エラーメッセージを受け取る処理」だとわかりやすくなります。
C#ErrorHandler handler = message =>
{
Console.WriteLine($"エラー: {message}");
};
ライブラリや大規模なアプリケーションでは、型名そのものがドキュメントの役割を持ちます。そのため、意味を明確にしたい場合は自作delegateが有効です。
7-4. Actionで十分なケース
一方、簡単なコールバックや短い処理であれば、Actionで十分です。
C#void Execute(Action action)
{
action();
}
このようなケースでわざわざ次のようなdelegateを作る必要はあまりありません。
C#delegate void ExecuteAction();
また、メソッドの中だけで使う一時的な処理にもActionが向いています。
C#Action log = () =>
{
Console.WriteLine("ログを出力します");
};
log();
使い分けの目安は次のとおりです。
| ケース | 選択 |
|---|---|
| 短い処理を渡したい | Action |
| 一時的なコールバック | Action |
| 型名で意味を表現したい | 自作delegate |
| APIとして外部に公開する | 自作delegateまたはevent |
| 可読性を重視したい | 状況に応じて判断 |
単純な戻り値なしの処理であれば、まずはActionを使うとよいでしょう。
8. Actionを使うときの注意点
Actionは便利ですが、使い方を間違えると可読性や保守性が下がることがあります。
ここでは、Actionを使うときに注意したいポイントを解説します。
8-1. 戻り値が必要な処理には使えない
Actionは戻り値を返せません。
C#Action action = () =>
{
Console.WriteLine("Hello");
};
次のように値を返す処理はActionにはできません。
C#// エラー
// Action action = () => 100;
戻り値が必要な場合はFuncを使います。
C#Func<int> func = () =>
{
return 100;
};
int result = func();
引数を受け取り、戻り値を返す場合もFuncです。
C#Func<int, int, int> add = (a, b) =>
{
return a + b;
};
Actionは「実行するだけ」、Funcは「実行して結果を返す」と考えましょう。
8-2. null参照例外を防ぐ書き方
Actionはnullになる可能性があります。
C#Action action = null;
この状態で呼び出すと、NullReferenceExceptionが発生します。
C#// action(); // 例外
安全に実行するには、nullチェックを行います。
C#if (action != null)
{
action();
}
または、次のように書けます。
C#action?.Invoke();
コールバック引数としてActionを受け取る場合も、nullを許可するなら?.Invoke()を使うと安全です。
C#void Execute(Action action)
{
Console.WriteLine("処理開始");
action?.Invoke();
Console.WriteLine("処理終了");
}
ただし、必ず処理を渡してほしい設計なら、nullチェックして例外を投げることもあります。
C#void Execute(Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
action();
}
nullを許可するのか、許可しないのかを設計として明確にすることが重要です。
8-3. 可読性が下がるActionの使い方
Actionは便利ですが、使いすぎるとコードが読みにくくなります。
たとえば、次のようなコードは処理の意味がわかりにくいです。
C#void Execute(Action a, Action b, Action c)
{
a();
b();
c();
}
a、b、cが何をする処理なのか、名前から判断できません。
改善するなら、引数名を具体的にします。
C#void Execute(Action beforeAction, Action mainAction, Action afterAction)
{
beforeAction?.Invoke();
mainAction?.Invoke();
afterAction?.Invoke();
}
また、ラムダ式が長すぎる場合も読みにくくなります。
C#Execute(() =>
{
// 何十行もの処理
});
このような場合は、メソッドに切り出したほうがよいです。
C#void SaveUser()
{
// 保存処理
}
Execute(SaveUser);
Actionは処理を柔軟に渡せる反面、処理の流れが追いにくくなることがあります。名前付けと分割を意識して使いましょう。
8-4. 例外処理をどこで行うべきか
Actionの中で例外が発生した場合、その例外は呼び出し元に伝わります。
C#Action action = () =>
{
throw new Exception("エラーが発生しました");
};
action();
Actionを受け取るメソッド側で例外処理を行うこともできます。
C#void SafeExecute(Action action)
{
try
{
action();
}
catch (Exception ex)
{
Console.WriteLine($"例外を処理しました: {ex.Message}");
}
}
呼び出し側で例外処理を行うこともできます。
C#try
{
SafeExecute(() =>
{
throw new Exception("エラー");
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
どちらで例外処理をするべきかは、設計によります。
共通的にログを残したい場合は、Actionを受け取る側で例外処理を行うと便利です。
C#void ExecuteWithLogging(Action action)
{
try
{
action();
}
catch (Exception ex)
{
Console.WriteLine($"ログ出力: {ex.Message}");
throw;
}
}
ただし、例外を握りつぶすと問題に気づきにくくなります。
C#catch
{
// 何もしないのは避けたほうがよい
}
例外を処理する場合は、ログ出力、再スロー、ユーザーへの通知など、意図を明確にしましょう。
8-5. ラムダ式のキャプチャに注意する
ラムダ式では、外側の変数を参照できます。
C#int count = 0;
Action action = () =>
{
count++;
Console.WriteLine(count);
};
action(); // 1
action(); // 2
このように、ラムダ式が外側の変数を保持することをキャプチャと呼びます。
キャプチャは便利ですが、意図しない動作につながることがあります。
たとえば、ループ内でActionを作る場合です。
C#List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
このようなコードでは、変数iの扱いに注意が必要です。期待した値にならない場合は、ループ内で別の変数にコピーすると安全です。
C#List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int index = i;
actions.Add(() => Console.WriteLine(index));
}
foreach (var action in actions)
{
action();
}
ラムダ式で外側の変数を使うときは、「その変数の値がいつ評価されるのか」を意識しましょう。
9. Actionに関するよくある質問
ここでは、C#のActionについて初心者が疑問に感じやすいポイントをQ&A形式で整理します。
9-1. Actionとメソッドの違いは何ですか?
メソッドは、クラスや構造体などに定義された処理そのものです。
C#void SayHello()
{
Console.WriteLine("Hello");
}
一方、Actionは、そのメソッドやラムダ式を代入できる型です。
C#Action action = SayHello;
action();
つまり、メソッドは「処理の定義」、Actionは「処理を入れておける変数」と考えるとわかりやすいです。
Actionを使うことで、メソッドを別のメソッドに渡したり、後から実行したりできます。
C#void Execute(Action action)
{
action();
}
Execute(SayHello);
9-2. ActionとFuncはどちらを使えばいいですか?
戻り値が不要ならActionを使います。
C#Action action = () =>
{
Console.WriteLine("表示するだけ");
};
戻り値が必要ならFuncを使います。
C#Func<int> func = () =>
{
return 10;
};
判断基準はシンプルです。
| やりたいこと | 使う型 |
|---|---|
| 処理を実行するだけ | Action |
| 処理結果を返したい | Func |
| true/falseを返す条件判定 | PredicateまたはFunc<T, bool> |
「この処理の結果を後で使うか?」を考えると選びやすくなります。
9-3. Actionに戻り値を持たせることはできますか?
できません。
Actionは戻り値がvoidの処理を表すため、値を返すことはできません。
C#// エラー
// Action action = () => 123;
戻り値を持たせたい場合は、Funcを使います。
C#Func<int> func = () => 123;
int result = func();
引数がある場合もFuncを使います。
C#Func<int, int, int> add = (a, b) => a + b;
int result = add(1, 2);
戻り値が必要かどうかで、ActionとFuncを使い分けましょう。
9-4. Actionは非同期処理でも使えますか?
使えますが、注意が必要です。
次のように、asyncラムダ式をActionに代入することはできます。
C#Action action = async () =>
{
await Task.Delay(1000);
Console.WriteLine("完了");
};
しかし、この場合はasync voidに近い扱いになります。async voidは例外を捕捉しにくく、処理の完了も待ちにくいため、通常の非同期処理ではおすすめされません。
非同期処理を渡したい場合は、Func<Task>を使うのが一般的です。
C#Func<Task> action = async () =>
{
await Task.Delay(1000);
Console.WriteLine("完了");
};
await action();
引数がある場合は、次のように書けます。
C#Func<string, Task> action = async message =>
{
await Task.Delay(1000);
Console.WriteLine(message);
};
await action("非同期処理が完了しました");
非同期処理では、ActionよりもFunc<Task>を優先して使うと安全です。
9-5. Actionは初心者でも覚えるべきですか?
はい、C#を学ぶならActionは覚えておくべきです。
理由は、実際のC#コードでよく使われるからです。
たとえば、次のような場面で登場します。
| 場面 | Actionの使われ方 |
|---|---|
| コールバック | 処理完了後に呼ぶ |
| イベント | 何かが起きたときに呼ぶ |
| List.ForEach | 各要素に対して処理する |
| Unity | ゲーム内イベントや完了通知 |
| 共通処理 | 一部の処理だけ差し替える |
最初は難しく感じるかもしれませんが、基本はシンプルです。
C#Action action = () =>
{
Console.WriteLine("処理");
};
action();
Actionは「戻り値のない処理を変数に入れるもの」と覚えれば十分です。
そこから、引数ありのAction<T>、Funcとの違い、ラムダ式との組み合わせを順番に理解していけば問題ありません。
まとめ
C#のActionは、戻り値のない処理を変数のように扱える組み込みデリゲートです。
C#Action action = () =>
{
Console.WriteLine("Hello");
};
action();
Actionを使うと、メソッドやラムダ式を変数に代入したり、別のメソッドの引数として渡したりできます。
引数がない場合はAction、引数が1つならAction<T>、引数が2つならAction<T1, T2>のように書きます。
C#Action<string> print = message =>
{
Console.WriteLine(message);
};
print("こんにちは");
ActionとFuncの違いは、戻り値の有無です。
| 型 | 戻り値 | 使う場面 |
|---|---|---|
Action | なし | 実行するだけの処理 |
Func | あり | 結果を返す処理 |
Predicate | bool | 条件判定 |
戻り値が不要ならAction、戻り値が必要ならFuncを使いましょう。
また、Actionはラムダ式と組み合わせることで、短い処理を簡潔に書けます。
C#Action<int> showNumber = number =>
{
Console.WriteLine(number);
};
showNumber(10);
実践では、コールバック、イベント的な処理、List.ForEach、共通処理の切り替え、Unityなどでよく使われます。
ただし、Actionを使うときは、戻り値がないこと、nullチェックが必要なこと、ラムダ式のキャプチャ、非同期処理ではFunc<Task>を検討すべきことに注意しましょう。
ActionはC#の理解を深めるうえで重要な機能です。まずは「戻り値なしの処理を入れる型」として覚え、実際にコードを書きながら少しずつ慣れていきましょう。

