C#における ref と out の違いを整理する

システム開発
スポンサーリンク
スポンサーリンク

C#でメソッド設計をしていると、「値を参照渡ししたいだけなのに、ref と out のどちらを使うべきか迷う」という場面に遭遇することがあります。

どちらも参照渡しを行うキーワードですが、設計意図・責任の所在・APIの読みやすさという観点では明確な違いがあります。

本記事では、ref と out の仕様差を整理したうえで、

コードレビューやAPI設計の場面で「なぜその修飾子を選んだのか」を説明できる状態になることをゴールとします。


ref と out の基本仕様の違い

結論から言うと、

「入力を含むなら ref」「出力専用なら out」

と覚えるのが基本です。

ref と out はどちらも C# における参照渡しの手段ですが、使用タイミングと設計意図が異なります。以下は、両者の違いを仕様レベルで整理したものです。

基本的な仕様の違い

  • 呼び出し前の初期化
    • ref:必須
    • out:不要
  • メソッド内での代入
    • ref:任意
    • out:必須
  • 主な用途
    • ref:入出力(双方向)
    • out:出力専用

ref は「すでに存在する値を使い、必要に応じて更新する」ことを前提としています。

一方、out は「メソッド側が値を生成・初期化する」ことを明確に表現するための仕組みです。

この違いによって、APIが何を期待しているのかがコード上で明確になるという大きな利点があります。


ref の特徴と利用シーン

ref を使うことで、メソッドに渡された引数を参照で扱い、元の変数の値を直接読み書きできます。

これは、既存の値を前提に処理を進めたいケースで有効です。

ref が適しているケース

  • 既存の値を元に、メソッド内で更新を行いたい場合
  • 大きな構造体をコピーせず、パフォーマンスを重視したい場合
  • メソッド内外で値の状態変化を追跡したい場合

使用例

voidUpdateValue(refint number)
{
    number +=10;
}

int x =5;
UpdateValue(ref x);// x は 15 になる

この例では、x は呼び出し前から有効な値を持っており、その値を元に処理が行われています。

ref はこのように、**「入力を含む前提での更新処理」**に適しています。


ref 使用時の設計上の注意点

ref は非常に強力ですが、同時に注意が必要なキーワードでもあります。

ref を使うと、メソッドの戻り値だけを見ても、どの変数が変更されたのか分かりにくくなるという副作用が発生しやすくなります。

この「副作用」とは、処理の影響範囲がコードから直感的に読み取りづらくなる状態を指します。

注意すべきポイント

  • 公開APIやインターフェースでは使用を慎重に検討する
  • ローカルメソッド(private / internal)に限定するのが無難
  • ref を使う理由と影響範囲をコメントや設計書で明示する

ref は「値を変更する可能性がある」という含みを持つため、

意図が不明確なまま使うと、保守性やテスト容易性を損なう原因になります。


out の特徴と利用シーン

out は、メソッドから値を返すための手段として設計されています。

特に、「処理の成否」と「結果の値」を同時に返したいケースでよく使われます。

out が適しているケース

  • 成功/失敗を bool で返し、結果を別で受け取りたい場合
  • TryParse のように、成功時のみ値が有効になるケース
  • 呼び出し側で事前に値を用意する意味がない場合

標準的な例

boolTryParse(string s,outint result)
{
returnint.TryParse(s,out result);
}

この形により、呼び出し側は

「このメソッド内で result が必ず初期化される」

という前提で安全にコードを書くことができます。


out を使う設計上のメリット

out は出力専用であるため、責務が非常に明確です。

  • 出力専用のため、副作用の範囲が限定されやすい
  • API 利用者がメソッドの責任範囲を理解しやすい
  • 複数の値を返したい場合の表現として自然

ref と異なり、呼び出し元で初期化が不要な点も、

「値はメソッドが責任を持って生成する」という設計意図を明確にします。


ref と out の設計思想の違い

ref と out の本質的な違いは、**「責任の所在」**にあります。

  • ref:初期値は呼び出し元が準備する(値の出入りがある)
  • out:初期化はメソッド側が行う(出力専用)

この違いにより、API を利用する開発者は

**「この引数に何を期待すればよいのか」**を直感的に判断できます。

例えば、TryParse 系メソッドで ref を使ってしまうと、

呼び出し側が初期値を用意する必要が生じ、設計が不自然になります。

out を使うことで、責務と意図が明確に分離されます。


ref / out を使わないという選択肢

近年の C# では、ref や out を使わなくても可読性の高いコードを書ける選択肢が増えています。

判断の目安

  • 戻り値が1つで足りる → 通常の return
  • 複数の値を返したい → tuple や record
  • 状態を変更したい → クラス設計で責務を分離

tuple の例(C# 7.0以降)

(string firstName,string lastName) GetName()
{
return ("John","Doe");
}

var (first, last) = GetName();

このように、意図が明確で副作用の少ない設計が可能です。

ref や out は「最後の手段」として位置づけるのが、現代的な設計と言えます。

※ なお、C# 7.2以降では in キーワードにより

「参照渡しだが変更不可」という選択肢も提供されています。

パフォーマンス目的の ref は、in で代替できる場合もあります。


実務での使い分け指針(まとめ)

迷ったときは、次のガイドラインを基準にすると判断しやすくなります。

  • 新しい値を返すだけなら out を使う
  • 既存の値を更新するなら ref を使う
  • そもそも ref / out が不要なら使わない
  • 公開APIでは副作用を最小限に抑える設計を優先する

まとめ

ref と out は、C#において非常に表現力の高いキーワードです。

しかし、「使えるから使う」のではなく、設計意図をコードに表現するための手段として使い分けることが重要です。

特に API 設計やコードレビューの場面では、

  • なぜ ref にしたのか
  • なぜ out を選んだのか

を説明できるかどうかが、設計の質を左右します。

既存コードを ref / out の観点で見直すだけでも、

設計意図や改善ポイントが浮かび上がってくるはずです。

ぜひ日常の開発で、意識的に使い分けてみてください。

システム開発
スポンサーリンク
シェアする
tobotoboをフォローする

コメント

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