動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

C# でメソッドから複数の戻り値を返す3つの書き方(out 引数 / ValueTuple / 専用クラス)

バイブス父さん
現役の業務SE
2026年7月8日11 min read
C# でメソッドから複数の戻り値を返す3つの書き方(out 引数 / ValueTuple / 専用クラス)

みなさんこんにちは!ヒロポンです!

C# で「1つのメソッドから複数の戻り値を返したい」時、みなさんどう書いてますか??

計算した結果と、成功したかどうか。開始日と終了日をセットで。業務系の画面まわりを触ってると、こういう場面が地味に多いんですよね。

私は長いこと、この手のやつをぜんぶ out 引数で書いてました。で、ある日、自分のコードを見返した。引数が out だらけで、呼び出し側がもう何がなんだか分からん。自分で書いたのに、うわ読みにくい…ってなったやつです。

というわけで今回は、C# で複数の戻り値を返す書き方を out 引数 / ValueTuple / 専用クラス の3つに整理します。それぞれのコードと、現場でどう使い分けるかまで。コピペで動くやつを載せてるので、手元で試しながら読んでもらえると早いです。

結論: out / ValueTuple / 専用クラスはこう使い分ける

先に答えを出します。C# で複数の戻り値を返す時は、こう考えるとラクです。

  • out 引数 … 返したい値が実質1個で、成功したかをおまけで返すだけなら(int.TryParse みたいなやつ)
  • ValueTuple … 2〜3個の値をサッと返したい時。今はこれが一番手軽
  • 専用クラス … 返す値が増えてきて、それぞれに名前や意味を持たせたい時

ValueTuple というのは、(int, string) みたいに複数の値をまとめて1つとして扱える構造体(値型)のことです。C# 7 から入った書き方で、これを覚えると out 地獄からだいぶ解放されます。

まずはそれぞれ、実際のコードで見ていきましょう。

なぜ out 引数だらけになるのか

そもそもなんで out を使うのか。C# のメソッドは、return で返せる値が基本1個だけだからです。

2個以上返したいとなると、素直に思いつくのが out 引数なんですよね。呼び出し側の変数に直接書き込んでもらう形です。

これ、返す値が増えるほど呼ぶ側がつらくなります。Method(a, b, out x, out y, out z) みたいになると、どれが入力でどれが出力なのか、パッと見で分からない。ん?これ全部 out にする必要ある?って自分でも思うやつです。

そこで登場するのが ValueTuple と専用クラスです。

書き方1: out 引数 — 1個だけ「ついで」に返すなら今も現役

まずは基本の out から。入力値をチェックして、OKかどうかとNGの理由を返す例で見ていきます。

// 入力チェック: 成功なら true、失敗なら false + 理由を out で返す
public static bool Validate(string input, out string error)
{
    if (string.IsNullOrEmpty(input))
    {
        error = "未入力です";
        return false;
    }
    if (input.Length > 20)
    {
        error = "20文字以内で入力してください";
        return false;
    }
    error = "";
    return true;
}

呼び出し側はこんな感じで書けます。

if (!Validate(userName, out string error))
{
    Console.WriteLine(error);  // NG の時だけ理由が入る
}

実行結果:

out 引数版 Validate の実行結果。有効・空文字・21文字超の3ケースで期待通り分岐する

これ、int.TryParse とまったく同じ形なんですよね。成功したかを bool で返して、中身は out で受け取る。返す値が実質1個(+成功フラグ)の時は、out が今でも一番素直です。

逆に、返す値が2個3個と増えてくると out はしんどい。次いきましょう。

書き方2: ValueTuple — 2〜3個をサッと返す定番

C# 7 以降なら、複数の値は ValueTuple で返すのが一番手軽です。さっきの検証を書き換えるとこうなります。

// (成功したか, 理由) をまとめて返す
public static (bool ok, string error) Validate2(string input)
{
    if (string.IsNullOrEmpty(input))
        return (false, "未入力です");
    if (input.Length > 20)
        return (false, "20文字以内で入力してください");
    return (true, "");
}

呼び出し側は、いい感じに分解して受け取れます。

var (ok, error) = Validate2(userName);
if (!ok)
{
    Console.WriteLine(error);
}

実行結果:

ValueTuple 版 Validate2 の実行結果。var (ok, error) = ... の分解代入で ok と error を受け取れている

コツは、(bool ok, string error) のように 名前付き にしておくこと。ok error という名前で受け取れるので、呼ぶ側が読みやすい。名前を付けない (bool, string) でも動くには動きますが、その場合は .Item1 .Item2 でアクセスすることになって、結局分かりにくくなります。

これ、どっちが読みやすいと思いますか??私は断然、名前付きの方が好きです。

書き方3: 専用クラス — 値に「意味」が増えてきたら

返す値が4個5個と増えたり、その塊を他のメソッドにも渡し回したくなったら。専用のクラスを作った方がスッキリします。

// 検証結果を表すクラス
public class ValidationResult
{
    public bool Ok { get; set; }
    public string Error { get; set; }
}

public static ValidationResult Validate3(string input)
{
    if (string.IsNullOrEmpty(input))
        return new ValidationResult { Ok = false, Error = "未入力です" };
    if (input.Length > 20)
        return new ValidationResult { Ok = false, Error = "20文字以内で入力してください" };
    return new ValidationResult { Ok = true, Error = "" };
}

クラスにしておくと、後から「エラーコードも足したい」となった時に、プロパティを1個追加するだけで済みます。ValueTuple だと戻り値の型をあちこち直すことになるので、育っていく値の塊はクラスの方が強いです。

C# 9(.NET 5 以降)が使える環境なら、record を使うと public record ValidationResult(bool Ok, string Error); の1行で同じものが書けます。ただ Framework 4.7.2 だと record は使えないので、その場合は上の普通の class でいきましょう。

before / after: out 引数だらけを ValueTuple に直す

じゃあ実際に out だらけのメソッドを ValueTuple に書き換えると、どのくらいスッキリするのか。配列の合計・最大・最小をまとめて返す例で比べてみます。

out 引数3つで返していた集計メソッドを、名前付き ValueTuple の戻り値ひとつに書き換える before/after

実行結果(after・ValueTuple 版。before の out 3つ版と同じ sum/max/min を返す):

ValueTuple 版 Summarize の実行結果。sum=6 max=3 min=1 を戻り値ひとつで返している

after の方、呼ぶ側が var (s, mx, mn) = Summarize(nums); の1行で済むのが気持ちいい。入力と出力がごちゃ混ぜにならないので、後から読む人にやさしいんですよね。めっちゃスッキリする!!

結局どれを使う? 3つの使い分け早見表

観点ごとに3つを並べるとこうなります。

out 引数 / ValueTuple / 専用クラスを手軽さ・読みやすさ・値の数・拡張性・Framework 4.7.2 対応で比較した早見表

out はTryパターンでOK/NGを返すだけに絞る。日常の複数戻り値は ValueTuple。値が育ってきたらクラス。だいたいこの3段で整理できます。

ハマりポイント3つ(知らないと地味に時間を溶かす)

使い分けの話はここまで。ここからは、私が実際に踏んだハマりどころを3つ。どれも知ってれば10分、知らないと数十分持っていかれるやつです。

① ValueTuple が見つからないと言われたら

古い環境でタプル構文を書くと、型または名前空間名 'ValueTuple' が見つかりませんでした みたいなエラーが出ることがあります。

これ、Framework のバージョンで事情が変わります。System.ValueTuple が framework 本体に入ったのは .NET Framework 4.7 から で、4.7.2 なら標準で使えます(NuGet 不要)。逆に 4.6.2 以前 だと framework に無いので、NuGet で System.ValueTuple を足す必要があります。

ややこしいのは、4.7 以降でも参照設定や VS の版の都合で、たまに同じエラーが出ること。その時も NuGet で System.ValueTuple を足せば直ります。まずは自分の環境のバージョン確認が先です。

② 名前付きタプルの名前が消える

名前付きの (bool ok, string error) は便利なんですが、この名前、実は コンパイラが付けてくれる糖衣 でしかありません。

なのでリフレクションや dynamic 越し、別アセンブリの境界をまたぐと、名前が Item1 Item2 に戻ることがあります。私も昔、別プロジェクトから受け取ったタプルの .ok が使えなくて「あれ、名前どこいった?」ってなって30分溶かしました。実行時に名前が保証されるわけじゃない、というのは頭の隅に置いておくとよいです。

③ out 引数は EF のクエリ(式ツリー)に書けない

メモリ上の List に対する LINQ なら、out var をラムダの中に書けます。

// メモリ上の List に対してならOK
var oks = names.Where(n => Validate(n, out var _)).ToList();

ところが、これを EF(Entity Framework)の IQueryable に対して書くと、式ツリーに out 引数変数の宣言を含めることはできません(CS8198)というエラーになります。DB 側に投げるクエリは式ツリーに変換されるからです。

DB アクセスと絡めて複数戻り値を扱うなら、out より (bool ok, ...) を返すメソッドにしておく。そうすれば、この制約に引っかかりません。

現場メモ: 戻り値の型は「呼ぶ側の読みやすさ」で決める

戻り値の書き方って、動けばどれでもいい話に見えて、正直、保守で効いてきます。

前に流通系の基幹システムの保守で、集計処理が out 5個で値を返してるメソッドがあって。呼び出し箇所が10箇所以上、どれも out int a, out int b, ... で受けてたんですよね。1個 out を足すたびに、全呼び出し側を直して回る羽目になってました。

これ、最初から名前付き ValueTuple か専用クラスで返してたら、呼ぶ側はほぼ触らずに済んだんです。戻り値の設計は、書く時のラクさより「半年後に読む人・直す人のラクさ」で決める。そう考えると、だいたい外しません。

まとめ

C# で複数の戻り値を返す3つの書き方、こんな感じで覚えておけば十分です。

  • outTryパターンで1個返し に絞る
  • 日常の2〜3個は 名前付き ValueTuple
  • 値が育ってきたら 専用クラス(新しめの環境なら record)

「とりあえず out」で書いてた人は、まず ValueTuple に置き換えるだけで呼ぶ側がだいぶ読みやすくなります。手元のメソッド1個からでも試してみてください。

よくある質問

Q1. ValueTuple と昔の Tuple は何が違いますか?

ValueTuple は構造体(値型)で、Tuple<T> は参照型です。ValueTuple はヒープに乗らないぶん軽くて、(int, string) という構文や名前付き要素が使えます。C# 7 以降は基本 ValueTuple でいいです。

Q2. Framework 4.7.2 で ValueTuple は使えますか?

使えます。System.ValueTuple は .NET Framework 4.7 から framework 標準で同梱されているので、4.7.2 なら NuGet なしでタプル構文が動きます。NuGet が要るのは 4.6.2 以前です。4.7 以降でも参照設定の都合でエラーが出ることが稀にあり、その時は NuGet で System.ValueTuple を足せば直ります。

Q3. out 引数はもう古いですか?

古くないです。int.TryParse のように成功したかと結果をセットで返す用途では、out が今でも一番素直です。返す値が1個で、if で受けたい時に向いてます。

Q4. record を使えばもっと短く書けますか?

書けます。C# 9(.NET 5 以降)が使えるなら、専用クラスを record で1行定義できます。ただ Framework 4.7.2 では record が使えないので、その場合は普通の class でいきましょう。

次に読むべき記事

以上!

同じところでハマってる人がいたら、どんどんシェア待ってるぜ!!

この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について