C# のメソッド引数は、修飾子なしでは値のコピーが渡される(値渡し)のが基本です。out・ref・in・params の 4 つの修飾子を使うと、それぞれ異なる渡し方ができます。
修飾子なし(値渡し)
修飾子がない場合、呼び出し元の変数のコピーがメソッドに渡されます。メソッド内で値を変更しても、呼び出し元の変数には影響しません。
void Double(int x)
{
x = x * 2; // コピーを変更
}
int value = 5;
Double(value);
Console.WriteLine(value); // 5(変わらない)
ref — 参照渡し
ref を使うと、変数の**参照(アドレス)**がメソッドに渡されます。メソッド内での変更が呼び出し元の変数に反映されます。
void Double(ref int x)
{
x = x * 2; // 呼び出し元の変数を変更
}
int value = 5;
Double(ref value);
Console.WriteLine(value); // 10
ref のルール
- 呼び出し元でも
refを明示する必要があります(Double(ref value)) - 渡す変数は事前に初期化されていなければなりません
- 変数(
var・フィールド・配列の要素など)しか渡せません(リテラルや式は不可)
主な用途
複数の値を返したいが構造体やタプルにまとめたくない場合、または大きな構造体をコピーなしに書き換えたい場合に使います。
void Swap(ref int a, ref int b) => (a, b) = (b, a);
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}"); // 2, 1
out — 出力パラメータ
out は ref と似ていますが、メソッド内で必ず値を代入しなければならないという制約があります。呼び出し元では初期化せずに渡せます。
bool TryParse(string input, out int result)
{
if (int.TryParse(input, out int value))
{
result = value;
return true;
}
result = 0; // 必ず代入が必要
return false;
}
if (TryParse("42", out int number))
{
Console.WriteLine(number); // 42
}
out のルール
- 呼び出し元でも
outを明示する(TryParse("42", out int number)) - 呼び出し元で変数を事前初期化しなくてよい
- メソッド内ですべてのコードパスで値を代入しないとコンパイルエラー
インライン宣言(C# 7.0)
C# 7.0 から out の宣言を呼び出し箇所でインライン記述できるようになりました。
// 事前に変数を宣言しなくてよい
if (int.TryParse("123", out int value))
{
Console.WriteLine(value);
}
ref と out の違い
ref |
out |
|
|---|---|---|
| 呼び出し元での初期化 | 必要 | 不要 |
| メソッド内での代入 | 任意 | 必須 |
| 主な用途 | 読み書き両方 | 出力専用 |
in — 読み取り専用参照渡し(C# 7.2)
in は C# 7.2 で追加された修飾子です。ref と同様に参照を渡しますが、メソッド内で値を変更できない(読み取り専用)という制約があります。
void PrintPoint(in Point p)
{
Console.WriteLine($"({p.X}, {p.Y})");
// p.X = 0; // コンパイルエラー(変更不可)
}
var point = new Point(3, 4);
PrintPoint(in point);
// または修飾子なしで呼び出すことも可能
PrintPoint(point);
in のルール
- 呼び出し元で
inを明示する(省略も可能) - 渡す変数は事前に初期化されていなければならない
- メソッド内で値を変更するとコンパイルエラー
in の主な用途
大きな struct をコピーなしに渡しつつ、変更されないことを保証したい場合に有効です。
readonly struct Matrix4x4
{
// 4x4 行列(64 バイト)
}
// コピーを発生させずに参照渡し、かつ変更を禁止
double Determinant(in Matrix4x4 m) { /* ... */ }
小さな型(int・double など)では参照の管理コストがコピーコストを上回ることもあるため、大きな struct への適用が主な使い所です。
params — 可変長引数
params を使うと、呼び出し元で任意の個数の引数を列挙して渡せます。メソッド内では配列として受け取ります。
int Sum(params int[] numbers)
{
return numbers.Sum();
}
Console.WriteLine(Sum(1, 2, 3)); // 6
Console.WriteLine(Sum(10, 20)); // 30
Console.WriteLine(Sum()); // 0(空の配列)
Console.WriteLine(Sum(new[] { 1, 2 })); // 配列を直接渡すことも可能
params のルール
paramsはパラメータリストの最後の 1 つだけに付けられますparamsパラメータの型は配列型でなければなりません(C# 12 以前)- 呼び出し側で配列を直接渡すことも、個別に列挙することも両方できます
他のパラメータとの組み合わせ
string Format(string separator, params string[] values)
=> string.Join(separator, values);
Console.WriteLine(Format(", ", "A", "B", "C")); // A, B, C
C# 13 の params の拡張
C# 13 で params が配列以外のコレクション型にも対応しました。
// C# 13 以降:Span<T> も使用可能
int Sum(params ReadOnlySpan<int> numbers)
{
int total = 0;
foreach (var n in numbers) total += n;
return total;
}
params ReadOnlySpan<T> を使うとヒープ割り当てを避けられ、パフォーマンスが改善します。
まとめ
| 修飾子 | 参照渡し | 呼び出し元での初期化 | メソッド内での変更 | 主な用途 |
|---|---|---|---|---|
| なし | ✗(値渡し) | 不要 | 不可(呼び出し元に影響なし) | 通常の引数 |
ref |
✓ | 必要 | 可 | 読み書き両用の参照渡し |
out |
✓ | 不要 | 必須(必ず代入) | 複数の戻り値・Try パターン |
in |
✓ | 必要 | 不可(読み取り専用) | 大きな struct の効率的な受け渡し |
params |
✗(値渡し) | — | 可 | 可変長引数 |