Unity & C# 学習教材

ref / out / in パラメータ

メソッドのパラメータは通常は値渡しです。refoutin を使うと、呼び出し元の変数を参照しながら値を書き換えたり、読み取ったりできます。

学習目標

前提知識


1. 値渡しとその限界

通常のパラメータは値渡しです。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

メソッドの中で x1 にしても、呼び出し元の v0 のままです。これは xv が別の変数だからです。


2. ref

ref は、呼び出し元の変数そのものを参照して受け取る書き方です。メソッド内で値を書き換えると、呼び出し元の変数も変わります。

書式:ref パラメータ

1
2
3
4
5
6
戻り値の型 メソッド名(ref 型 パラメータ名)
{
    処理
}

メソッド名(ref 変数名);
要素 説明
ref 参照渡しであることを示すキーワード
受け取る値の型
パラメータ名 メソッド内で使う名前
変数名 呼び出し元で渡す、すでに存在する変数

ref では、呼び出し前に変数が初期化済みでなければなりません。呼び出し先は「今ある値を読み取るかもしれない」からです。


3. out

out も参照渡しですが、目的は「呼び出し先から値を返すこと」です。

書式:out パラメータ

1
2
3
4
5
6
7
戻り値の型 メソッド名(out 型 パラメータ名)
{
    パラメータ名 = 値;
}

メソッド名(out 変数名);
メソッド名(out 型 変数名);
要素 説明
out 出力用の参照渡しであることを示すキーワード
受け取る値の型
パラメータ名 メソッド内で代入する名前
変数名 呼び出し元で受け取る変数

out では、呼び出し前の初期化は不要です。その代わり、メソッドを抜けるまでに必ず値を代入しなければなりません。また、out int x のようにインライン宣言も使えます。


4. in

in は参照渡しですが、読み取り専用です。呼び出し先は値を読めますが、書き換えられません。

書式:in パラメータ

1
2
3
4
5
6
戻り値の型 メソッド名(in 型 パラメータ名)
{
    処理
}

メソッド名(変数名);
要素 説明
in 読み取り専用の参照渡しであることを示すキーワード
受け取る値の型
パラメータ名 メソッド内で読み取る名前
変数名 呼び出し元で渡す変数

in は呼び出し側でのキーワード記述を省略できますref / out と異なる点です。メソッド名(変数名); と書いても、コンパイラが参照渡しとして扱います。

in は大きな struct(値型の構造体)をコピーせずに渡したいときに有効です。int のような小さなプリミティブ型では、効果はほとんどありません。


5. 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);v2 に更新された後の値を読み取っています。


よくあるミス

ミス①: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");
}

まとめ


理解度チェック

以下の問いに答えられるか確認しましょう。

  1. 通常の値渡しでは、メソッド内で int パラメータを書き換えても呼び出し元の変数が変わらないのはなぜですか?
  2. 次のコードの出力結果は何になりますか?

    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);
    
  3. out を使って、A.Nx5 を代入してから "A.N" を表示するメソッドを書いてください。呼び出し側はインライン宣言を使ってください。
解答を見る
  1. 値渡しでは値のコピーが渡されるためです。メソッド内のパラメータと呼び出し元の変数は別の変数です。
  2. 1
    2
    
       A.M
       4
    
  3. 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);
    

次のステップ

省略可能パラメータと名前付き引数 では、引数を省略したり、名前を付けて渡したりする書き方を学びます。