ホームに戻る
出典 :
値型 - C# リファレンス - C# | Microsoft Learn 参照型 - C# リファレンス - C# | Microsoft Learn in/out/refパラメーター修飾子の違いとは?[C#]:.NET TIPS - @IT
関連 :
配列 タプル レコード オブジェクトの代入とコピー [C/C++]const修飾の使い方
目次 :

値型と参照型

C#の型は「値型」と「参照型」に大別され、値型の変数にはその型のインスタンスが格納される(データを直接参照する)のに対し、参照型ではインスタンスへの参照が格納される。
代入時(メソッド呼び出しを含む)、参照型はインスタンスへの参照をコピーするが、値型はインスタンスの厳密なコピーを作成する。
これにより、参照型のコピー先(またはコピー元)の値を変更すると、同一のオブジェクトを指すコピー元(またはコピー先)の値も同様に変化するが、
値型はそれぞれが独立しており、一方の変更が他方には波及しない。

値型変数をメソッドに渡すと通常は「値渡し」となるため、変数のコピーを生成するためのコスト(オーバーヘッド)が発生する。
この場合、参照渡しを用いることでオーバーヘッドを低減することができる。

型の分類

in / ref / out による参照渡し

値型変数の参照をメソッドに渡す場合は、in / ref / out キーワードを付与する。
参照型変数に対して用いることも可能である。
修飾子 : 用途呼び出し前の
変数初期化
呼び出し時の
修飾子付与
メソッド内での
引数への割り当て
/更新
オプション引数
in : 入力必須任意不可
ref : 変更必須必須可能不可
out : 出力不要必須必須不可
(「オプション引数」はC++の「デフォルト引数」と同義。呼び出し時に指定の必要が無い(Optional)ことを意味する。)

in のサンプル

一般形
// 参照渡しメソッドの定義 public void InTest(in int arg1) { int localVal = arg1; } public void DoSomething() { int val = 3; // InTest() の呼び出し // (この場合、in キーワードは省略できる) InTest(in val); }
オプション引数を指定した場合
// 参照渡しメソッドの定義 public void InTest(in int arg1 = 0) { int localVal = arg1; } public void DoSomething() { // InTest() の呼び出し // (引数なしのため、arg1 = 0 となる) InTest(); }
エラーとなる場合
// 参照渡しメソッドの定義 public void InTest(in int arg1) { // メソッド内で in 修飾された引数を更新 ⇒ コンパイルエラー arg1 = 4; } public void DoSomething() { // InTest() の呼び出し // 引数 val が初期化されていない ⇒ コンパイルエラー InTest(int val); }
in 修飾はメソッド内での引数の書き換えを禁止するため、C/C++の const 修飾と同様に用いることができる。

ref のサンプル

一般形
// 参照渡しメソッドの定義 public void RefTest(ref int arg1) { int localVal = arg1; // メソッド内で引数(の参照先)を更新可能 arg1 = 4; } public void DoSomething() { int val = 3; // RefTest() の呼び出し // (ref キーワードは省略できない) RefTest(ref val); }
エラーとなる場合
// 参照渡しメソッドの定義 public void RefTest(ref int arg1) { int localVal = arg1; arg1 = 4; } public void DoSomething1() { // RefTest() の呼び出し // 引数 val が初期化されていない ⇒ コンパイルエラー RefTest(ref int val); } public void DoSomething2() { int val = 3; // RefTest() の呼び出し // ref キーワードが無い ⇒ コンパイルエラー RefTest(val); }

out のサンプル

一般形
// 参照渡しメソッドの定義 public void OutTest(out int arg1) { // メソッド内で引数(の参照先)に値を割り当てる(必須) arg1 = 4; } public void DoSomething() { int val = 3; // OutTest() の呼び出し // (out キーワードは省略できない) OutTest(out val); }
引数を初期化しない場合
// 参照渡しメソッドの定義 public void OutTest(out cMyClass arg1) { // メソッド内で引数(の参照先)に値を割り当てる(必須) arg1 = new cMyClass(); } public void DoSomething1() { // OutTest() に渡す変数 // (宣言のみで初期化は不要) cMyClass val; // OutTest() の呼び出し OutTest(out val); } public void DoSomething2() { // val の宣言と同時に OutTest() の呼び出し OutTest(out cMyClass val); }
エラーとなる場合
// 参照渡しメソッドの定義 public void OutTest(out int arg1) { // 引数を参照している ⇒ コンパイルエラー int localVal = arg1; arg1 = 4; }

注意が必要な点 (メソッドのオーバーロード)

in / ref / out の各キーワードは、バイナリ上での差異は存在しない。
このため、メソッドのオーバーロードに関して以下のような制約が発生する。
// 参照渡しメソッド(in)の定義 public void Func(in int arg1) { : } // Func() のオーバーロード ⇒ エラー // (シグネチャが競合) public void Func(ref int arg1) { : } // Func() のオーバーロード ⇒ エラー // (シグネチャが競合) public void Func(out int arg1) { : } // Func() のオーバーロード ⇒ 正常 // (値渡しのためシグネチャが異なる) // (ただし、in キーワードを付与しない場合は呼び出す関数が曖昧となる) public void Func(int arg1) { : }
Func(in int) が定義されているため、Func(ref int) や Func(out int) を定義しようとするとシグネチャの競合によりコンパイルエラーとなる。