ホームに戻る
出典 :
関連 :
目次 :
値型と参照型
C#の型は「値型」と「参照型」に大別され、値型の変数にはその型のインスタンスが格納される(データを直接参照する)のに対し、参照型ではインスタンスへの参照が格納される。
代入時(メソッド呼び出しを含む)、参照型はインスタンスへの参照をコピーするが、値型はインスタンスの厳密なコピーを作成する。
これにより、参照型のコピー先(またはコピー元)の値を変更すると、同一のオブジェクトを指すコピー元(またはコピー先)の値も同様に変化するが、
値型はそれぞれが独立しており、一方の変更が他方には波及しない。
値型変数をメソッドに渡すと通常は「値渡し」となるため、変数のコピーを生成するためのコスト(オーバーヘッド)が発生する。
この場合、参照渡しを用いることでオーバーヘッドを低減することができる。
型の分類
- 値型
- 単純型 (プリミティブ)
- 構造体 ( struct )
- 参照型
- クラス ( class )
- レコード ( record / record class )
- 配列
- string
in / ref / out による参照渡し
値型変数の参照をメソッドに渡す場合は、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) を定義しようとするとシグネチャの競合によりコンパイルエラーとなる。