return 文(C#)
メソッドから値を返す『return』文の基本構文です。『C#』の return は「メソッドの実行を終了して呼び出し元に制御を戻す」命令であり、戻り値の型に応じてさまざまな使い方があります。void 型メソッドでの途中終了、早期 return によるガード節、タプルを使った複数値の返却をあわせて解説します。
構文
// 値を返すメソッドのreturnです。
戻り値の型 メソッド名(引数) {
// 処理
return 値; // 呼び出し元に値を返してメソッドを終了します。
}
// voidメソッドのreturnです(値なし)。
void メソッド名(引数) {
if (条件) {
return; // 値を返さずメソッドを途中で終了します(早期return)。
}
// 条件が偽のときだけここに来ます。
}
// タプルで複数の値を返すreturnです。
(型1, 型2) メソッド名(引数) {
return (値1, 値2); // カッコで複数値をひとまとめにして返します。
}
// 名前付きタプルで返すreturnです。
(型1 名前1, 型2 名前2) メソッド名(引数) {
return (値1, 値2); // 受け取り側は .名前1 / .名前2 でアクセスします。
}
戻り値の型とreturnの対応
| 戻り値の型 | return の書き方 | 備考 |
|---|---|---|
void | return; または省略 | メソッドの末尾では return を省略できます。途中で抜けたい場合に明示します。 |
int / double などの値型 | return 値; | 型が一致しない場合はコンパイルエラーになります。 |
string / クラス型 | return 参照; | null を返すこともできます(null 許容型の場合)。 |
bool | return true; / return false; | 条件判定メソッドでよく使われます。 |
(型1, 型2) タプル | return (値1, 値2); | 複数値をまとめて返せます。C# 7以降で使えます。 |
Task<T> | return 値;(async メソッド内) | async メソッドでは return した値が Task にラップされます。 |
早期returnとガード節
メソッドの先頭で無効な引数や異常条件をチェックして即座に return することを「早期 return(early return)」または「ガード節(guard clause)」と呼びます。ネストが浅くなり、メソッドの主処理が読みやすくなります。
| スタイル | 特徴 |
|---|---|
| 早期 return あり(ガード節) | 異常系を先に弾いてメソッドの主処理を最後にまとめられます。ネストが浅くなります。 |
| 早期 return なし(if / else ネスト) | 正常系・異常系を if で囲むためネストが深くなりがちです。 |
サンプルコード
ReturnBasic.cs
using System;
class ReturnBasic {
// --- 値を返すメソッドです ---
// レベルを数値で受け取り、レベル名を文字列で返します。
static string GetLevelName(int level) {
switch (level) {
case 0: return "S";
case 1: return "A";
case 2: return "B";
case 3: return "C";
case 4: return "D";
default: return "Unknown";
}
// switch の各 case で return しているため、ここには到達しません。
}
// --- voidメソッドの途中returnです ---
// スコアが 0 以下の場合は処理を打ち切ります。
static void ShowScore(string name, int score) {
if (score <= 0) {
Console.WriteLine(name + " はスコアを持っていません。");
return; // ここで終了します。以下の処理は実行されません。
}
// score が 1 以上の場合だけここに来ます。
Console.WriteLine(name + " のスコア: " + score);
}
// --- bool を返す判定メソッドです ---
// レベルSかどうかを bool で返します。
static bool IsLevelS(int level) {
return level == 0; // 条件式をそのまま return できます。
}
static void Main() {
// GetLevelName の呼び出しです。
Console.WriteLine("=== レベル名の取得 ===");
for (int i = 0; i <= 4; i++) {
Console.WriteLine("レベル " + i + " → " + GetLevelName(i));
}
// voidメソッドの途中returnの確認です。
Console.WriteLine("\n=== スコアの表示 ===");
ShowScore("member_2", 1200);
ShowScore("member_6", 0); // 0のためreturnで終了します。
ShowScore("member_1", 8500);
// bool を返すメソッドの確認です。
Console.WriteLine("\n=== レベルS判定 ===");
Console.WriteLine("レベル0(S): " + IsLevelS(0)); // True
Console.WriteLine("レベル1(A): " + IsLevelS(1)); // False
}
}
コンパイルして実行すると次のようになります。
dotnet script ReturnBasic.cs === レベル名の取得 === レベル 0 → S レベル 1 → A レベル 2 → B レベル 3 → C レベル 4 → D === スコアの表示 === member_2 のスコア: 1200 member_6 はスコアを持っていません。 member_1 のスコア: 8500 === レベルS判定 === レベル0(S): True レベル1(A): False
ReturnGuardClause.cs
using System;
class ReturnGuardClause {
// --- ガード節なし(ネストが深くなる例)---
// メンバーの参加適性を判定します。
static string CheckEligibilityNested(string name, int level, int score) {
if (name != null && name != "") {
if (level <= 2) { // レベルB以上なら参加可能とします。
if (score >= 500) { // スコアが500以上必要とします。
return name + " は参加できます。";
} else {
return name + " はスコアが不足しています(" + score + ")。";
}
} else {
return name + " はレベルが不足しています(レベル " + level + ")。";
}
} else {
return "名前が空です。";
}
}
// --- ガード節あり(早期returnで同じ処理を書き直した例)---
// 異常系を先に弾くことでネストが浅くなります。
static string CheckEligibility(string name, int level, int score) {
// ガード節1: 名前の検証です。
if (name == null || name == "") {
return "名前が空です。";
}
// ガード節2: レベルの検証です。
if (level > 2) {
return name + " はレベルが不足しています(レベル " + level + ")。";
}
// ガード節3: スコアの検証です。
if (score < 500) {
return name + " はスコアが不足しています(" + score + ")。";
}
// すべてのガード節を通過した場合の正常処理です。
return name + " は参加できます。";
}
static void Main() {
Console.WriteLine("=== 参加適性チェック(ネストあり)===");
Console.WriteLine(CheckEligibilityNested("member_2", 1, 1200));
Console.WriteLine(CheckEligibilityNested("member_4", 3, 800));
Console.WriteLine(CheckEligibilityNested("member_3", 2, 200));
Console.WriteLine(CheckEligibilityNested("", 1, 1000));
Console.WriteLine("\n=== 参加適性チェック(ガード節)===");
// 同じ入力で同じ結果になることを確認します。
Console.WriteLine(CheckEligibility("member_2", 1, 1200));
Console.WriteLine(CheckEligibility("member_4", 3, 800));
Console.WriteLine(CheckEligibility("member_3", 2, 200));
Console.WriteLine(CheckEligibility("", 1, 1000));
}
}
コンパイルして実行すると次のようになります。
dotnet script ReturnGuardClause.cs === 参加適性チェック(ネストあり)=== member_2 は参加できます。 member_4 はレベルが不足しています(レベル 3)。 member_3 はスコアが不足しています(200)。 名前が空です。 === 参加適性チェック(ガード節)=== member_2 は参加できます。 member_4 はレベルが不足しています(レベル 3)。 member_3 はスコアが不足しています(200)。 名前が空です。
ReturnTuple.cs
using System;
class ReturnTuple {
// --- 通常の戻り値は1つだけです ---
// 名前しか返せません。
static string GetName(string name) {
return name;
}
// --- タプルで2つの値を返します ---
// 名前とレベルをまとめて返します。
static (string name, int level) GetMemberInfo(string name, int level) {
return (name, level); // カッコで複数の値をまとめて返します。
}
// --- タプルで3つの値を返します ---
// メンバーの詳細情報(名前・レベル・スコア)をまとめて返します。
static (string name, int level, int score) GetMemberDetail(
string name, int level, int score) {
return (name, level, score);
}
// --- タプルを計算処理に使います ---
// チーム全体の合計スコアと平均スコアを一度に返します。
static (int total, double average) CalcTeamScore(int[] scores) {
int total = 0;
foreach (int e in scores) {
total += e;
}
double average = (double)total / scores.Length;
return (total, average); // 合計と平均を一度に返します。
}
static void Main() {
// --- 名前付きタプルの受け取り方 ---
// .フィールド名 でアクセスします。
Console.WriteLine("=== メンバー情報の取得 ===");
var info = GetMemberInfo("member_1", 0);
Console.WriteLine("名前: " + info.name + " レベル: " + info.level);
// --- 分解代入(デコンストラクション)を使った受け取り方 ---
// var (変数1, 変数2) = メソッド() と書けます。
Console.WriteLine("\n=== 分解代入での受け取り ===");
var (name, level, score) = GetMemberDetail("member_5", 0, 7000);
Console.WriteLine("名前: " + name + " レベル: " + level + " スコア: " + score);
// --- チーム全体の集計 ---
Console.WriteLine("\n=== チームスコアの集計 ===");
int[] teamScores = { 1200, 900, 3000, 8500 };
var (total, average) = CalcTeamScore(teamScores);
Console.WriteLine("合計スコア: " + total);
Console.WriteLine("平均スコア: " + average.ToString("F1")); // 小数点1桁で表示します。
// --- タプルの各要素に直接アクセスします ---
Console.WriteLine("\n=== 複数メンバーの情報表示 ===");
string[] names = { "member_2", "member_3", "member_4", "member_1" };
int[] levels = { 1, 2, 3, 0 };
for (int i = 0; i < names.Length; i++) {
var member = GetMemberInfo(names[i], levels[i]);
// names[i] と levels[i] ではなく member.name / member.level を使います。
Console.WriteLine(member.name + " (レベル: " +
(member.level == 0 ? "S" : "Level " + member.level) + ")");
}
}
}
コンパイルして実行すると次のようになります。
dotnet script ReturnTuple.cs === メンバー情報の取得 === 名前: member_1 レベル: 0 === 分解代入での受け取り === 名前: member_5 レベル: 0 スコア: 7000 === チームスコアの集計 === 合計スコア: 13600 平均スコア: 3400.0 === 複数メンバーの情報表示 === member_2 (レベル: Level 1) member_3 (レベル: Level 2) member_4 (レベル: Level 3) member_1 (レベル: S)
よくあるミス
すべてのコードパスで return が不足している
戻り値が void 以外のメソッドでは、すべての分岐で return に到達する必要があります。一部の if 分岐だけに return を書いて else 側を忘れると、コンパイルエラー CS0161("not all code paths return a value")になります。
static string GetLabel(int value) {
if (value > 0) {
return "正の値";
}
}
static string GetLabel(int value) {
if (value > 0) {
return "正の値";
}
return "ゼロまたは負の値";
}
void メソッドで値を返そうとする
戻り値の型が void のメソッドで return 値; と書くとコンパイルエラーになります。値を返したい場合はメソッドの戻り値の型を変更してください。
static void GetMessage() {
return "hello"; // CS0127: void メソッドでは値を返せません。
}
概要
『C#』の return 文はメソッドの実行をその場で終了し、呼び出し元に制御を返します。戻り値の型が void 以外のメソッドでは、すべてのコードパスで return に到達しなければコンパイルエラーになります。switch や if / else で分岐しているときに一部のパスで return が抜けていないかコンパイラが検出してくれるため、早期に気づけます。
ガード節(早期 return)は「異常条件を先頭で弾く」書き方です。引数の null チェックや範囲チェックをメソッドの先頭にまとめることで、以降の処理が常に正常値を扱えるという前提が明確になり、if / else のネストを浅く保てます。参加適性チェックのサンプルで示したように、ネストが深いコードとガード節を使ったコードは同じ結果を返しますが、後者の方が読みやすくなります。
タプルによる複数値の返却は C# 7 以降で使えます。(型1 名前1, 型2 名前2) のように名前を付けておくと .名前1 でアクセスでき、var (変数1, 変数2) = メソッド() の分解代入(デコンストラクション)で個別の変数に展開することもできます。複数の値を返したいときに専用クラスを作るほどではない場合にタプルが便利です。非同期メソッドでは return した値が自動的に Task<T> にラップされます(『async / await』を参照)。分岐ごとに異なる値を返す場合は『switch 文 / switch 式』や『三項演算子 / null 条件演算子』も参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。