メソッドのパラメータは通常は値渡しです。ref、out、in を使うと、呼び出し元の変数を参照しながら値を書き換えたり、読み取ったりできます。
ref の使い方と、呼び出し前に初期化が必要な理由を理解できるout の使い方と、メソッド内で代入が必須であることを理解できるin が読み取り専用の参照渡しであることを説明できる通常のパラメータは値渡しです。int を渡すと、メソッドにはその値のコピーが渡されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
class A
{
public void M(int x)
{
x = 1;
Console.WriteLine($"A.M: {x}");
}
}
var a = new A();
int v = 0;
a.M(v);
Console.WriteLine(v);
1
2
A.M: 1
0
メソッドの中で x を 1 にしても、呼び出し元の v は 0 のままです。これは x と v が別の変数だからです。
refref は、呼び出し元の変数そのものを参照して受け取る書き方です。メソッド内で値を書き換えると、呼び出し元の変数も変わります。
書式:ref パラメータ
1
2
3
4
5
6
戻り値の型 メソッド名(ref 型 パラメータ名)
{
処理
}
メソッド名(ref 変数名);
| 要素 | 説明 |
|---|---|
ref |
参照渡しであることを示すキーワード |
型 |
受け取る値の型 |
パラメータ名 |
メソッド内で使う名前 |
変数名 |
呼び出し元で渡す、すでに存在する変数 |
ref では、呼び出し前に変数が初期化済みでなければなりません。呼び出し先は「今ある値を読み取るかもしれない」からです。
outout も参照渡しですが、目的は「呼び出し先から値を返すこと」です。
書式:out パラメータ
1
2
3
4
5
6
7
戻り値の型 メソッド名(out 型 パラメータ名)
{
パラメータ名 = 値;
}
メソッド名(out 変数名);
メソッド名(out 型 変数名);
| 要素 | 説明 |
|---|---|
out |
出力用の参照渡しであることを示すキーワード |
型 |
受け取る値の型 |
パラメータ名 |
メソッド内で代入する名前 |
変数名 |
呼び出し元で受け取る変数 |
out では、呼び出し前の初期化は不要です。その代わり、メソッドを抜けるまでに必ず値を代入しなければなりません。また、out int x のようにインライン宣言も使えます。
inin は参照渡しですが、読み取り専用です。呼び出し先は値を読めますが、書き換えられません。
書式:in パラメータ
1
2
3
4
5
6
戻り値の型 メソッド名(in 型 パラメータ名)
{
処理
}
メソッド名(変数名);
| 要素 | 説明 |
|---|---|
in |
読み取り専用の参照渡しであることを示すキーワード |
型 |
受け取る値の型 |
パラメータ名 |
メソッド内で読み取る名前 |
変数名 |
呼び出し元で渡す変数 |
in は呼び出し側でのキーワード記述を省略できます。ref / out と異なる点です。メソッド名(変数名); と書いても、コンパイラが参照渡しとして扱います。
in は大きな struct(値型の構造体)をコピーせずに渡したいときに有効です。int のような小さなプリミティブ型では、効果はほとんどありません。
ref / out / in をまとめて確認する1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A
{
public void M(ref int x) { x = 1; Console.WriteLine("A.M"); }
public void N(out int x) { x = 2; Console.WriteLine("A.N"); }
public void P(in int x) { Console.WriteLine($"A.P: {x}"); }
}
var a = new A();
int v = 0;
a.M(ref v);
Console.WriteLine(v); // 1
a.N(out v);
Console.WriteLine(v); // 2
a.N(out int w); // インライン宣言
Console.WriteLine(w); // 2
a.P(v);
1
2
3
4
5
6
7
A.M
1
A.N
2
A.N
2
A.P: 2
最後の a.P(v); は、直前に a.N(out v); で v が 2 に更新された後の値を読み取っています。
ref を付け忘れる1
2
3
4
5
6
7
8
9
10
11
12
13
class A
{
public void M(ref int x) { x = 1; Console.WriteLine("A.M"); }
}
var a = new A();
int v = 0;
// ❌ NG: 呼び出し側にも ref が必要
// a.M(v);
// ✅ OK: 定義側と呼び出し側の両方に ref を書く
a.M(ref v);
out パラメータに値を代入せずにメソッドを抜ける1
2
3
4
5
6
7
8
9
10
11
12
// ❌ NG: すべての経路で値を代入していないのでコンパイルエラー
// void M(out int x)
// {
// Console.WriteLine("A.M");
// }
// ✅ OK: メソッドを抜ける前に必ず代入する
void M(out int x)
{
x = 0;
Console.WriteLine("A.M");
}
ref は参照渡しで、呼び出し前に初期化が必要out は出力用の参照渡しで、メソッド内で必ず代入する必要があるin は読み取り専用の参照渡しで、大きな struct を渡すときに使われる以下の問いに答えられるか確認しましょう。
int パラメータを書き換えても呼び出し元の変数が変わらないのはなぜですか?次のコードの出力結果は何になりますか?
1
2
3
4
5
6
7
8
9
10
11
12
13
class A
{
public void M(ref int x)
{
x = x + 1;
Console.WriteLine("A.M");
}
}
var a = new A();
int v = 3;
a.M(ref v);
Console.WriteLine(v);
out を使って、A.N が x に 5 を代入してから "A.N" を表示するメソッドを書いてください。呼び出し側はインライン宣言を使ってください。1
2
A.M
4
1
2
3
4
5
6
7
8
9
10
11
12
class A
{
public void N(out int x)
{
x = 5;
Console.WriteLine("A.N");
}
}
var a = new A();
a.N(out int value);
Console.WriteLine(value);
省略可能パラメータと名前付き引数 では、引数を省略したり、名前を付けて渡したりする書き方を学びます。