C#の値渡しと参照渡し

.NET Core, .NET Framework, C#, ポインタ

C#のコードを書いていると、C/C++を書いているときと違って値渡しと参照渡しの意識が曖昧になるときがある。

これではいかん。ちょっと整理しておこう。

メソッドの引数に渡すものの種類によって値渡しと参照渡しは決まるが、具体的にはどんな風なのか、まとめてみる。

もくじ


int、stringなどのプリミティブ型

次のように、変数nをメソッドに渡し、メソッド内でn++した場合にどうなるか見てみよう。

int n = 0;
Console.WriteLine(n);
// メソッド内でn++する。
MyMethod(n);
Console.WriteLine(n);

コンソール出力は次のようになる。

0
0

メソッド実行前後で値が変わっていないので値渡しになっていることが分かる。

C/C++出身の人には不思議かもしれないけど、string型でも同様だ。

string s = "abc";
Console.WriteLine(s);
// メソッド内で s += "def" する。
MyMethod(s);
Console.WriteLine(s);

コンソール出力は次のようになる。

abc
abc

値渡しになっている。

クラス

参考のため、次のようなクラスを考える。

public class SampleClass
{
    public int SampleProperty { get; set; } = 0;
}

このクラスのインスタンスをメソッドに渡した場合にどうなるか考える。

例えば次のようにコードを書いたとする。

SampleClass sc = new SampleClass();
Console.WriteLine(sc.SampleProperty);
// メソッド内で sc.SampleProperty++ する。
MyMethod(sc);
Console.WriteLine(sc.SampleProperty);

このコードのコンソール出力は次のようになる。

0
1

メソッドの実行後にプロパティの値が変わっているので、インスタンスは参照渡しされたことが分かる。

構造体

参考のため、次のような構造体を考える。

public struct SampleStruct
{
    public int SampleMember;
}

この構造体をメソッドに渡した場合にどうなるか考える。

例えば次のようにコードを書いたとする。

SampleStruct ss = new SampleStruct { SampleMember = 0 };
Console.WriteLine(ss.SampleMember);

// メソッド内で ss.SampleMember++ する。
MyMethod(ss);

Console.WriteLine(ss.SampleMember);

このコードのコンソール出力は次のようになる。

0
0

メソッドの実行前後で構造体のメンバの値が変わっていないので、構造体は値渡しされたことが分かる。

配列

次のように、配列をメソッドに渡し、メソッド内で配列の要素の値を編集することを考える。

int[] arr = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(arr);
// メソッド内で arr[2] = 100 する。
MyMethod(arr);
Console.WriteLine(arr);

コンソール出力は次のようになる。

1, 2, 3, 4, 5
1, 2, 100, 4, 5

メソッド実行後に配列の要素が変わっているため、配列は参照渡しされたことが分かる。

パラメータ修飾子 in

パラメータ修飾子in とは、例えばメソッドの引数に次のように書き添えて使うもので、「入力専用の引数」であることを明示するために使う。

MyMethod(in n);

この in は、メソッドの定義とメソッドを呼び出すコードの両方に書く必要がある。

in を指定された引数は、メソッド内で値を書き換えようとするとコンパイルエラーとなるため、そもそもメソッドの中で値を書き換えること自体を考えることはほぼ無いけど、注意がある

パラメータ修飾子in を書いたとしても、前述のクラスのインスタンスと配列が渡された場合は通常通り参照渡しとなるため、インスタンスのプロパティや配列の要素は普通に編集できてしまう。

SampleClass sc = new SampleClass();
// メソッド内で、sc.SampleProperty++ すると、呼び出し元の値も変わる。
MyMethod(in sc);

パラメータ修飾子 out

パラメータ修飾子out とは、例えばメソッドの引数に次のように書き添えて使うもので、「出力専用の引数」であることを明示するために使う。

int n = 0;
MyMethod(out n);
// こうすると、この行で変数nの宣言も兼ねられる。
MyMethod(out int n);

この out は、メソッドの定義とメソッドを呼び出すコードの両方に書く必要がある。

プリミティブ型であればメソッド内部で何らかの値を設定する必要がある。さもないとコンパイルエラーになる。

クラスを使う場合は、必ずメソッド内部でインスタンスを生成する必要がある。

パラメータ修飾子 ref

パラメータ修飾子ref とは、例えばメソッドの引数に次のように書き添えて使うもので、「参照渡しする引数」であることを明示するために使う。

MyMethod(ref n);

この ref は、メソッドの定義とメソッドを呼び出すコードの両方に書く必要がある。

クラスのインスタンスと配列は元々参照渡しされるものなので、ref修飾子を付ける場面はないけど、

プリミティブ型や構造体では通常の値渡しではなく参照渡しに変更できる。

まとめ

  • intやstringなどのプリミティブ型 → 値渡し
  • クラスのインスタンス → 参照渡し
  • 構造体 → 値渡し
  • 配列 → 参照渡し
  • in修飾子はメソッドに渡された値の変更ができないけど、クラスのインスタンスと配列が渡された場合は参照渡しになるので普通に値の編集ができる。
  • out修飾子は、メソッド内で値の初期化が必要。
  • 通常は値渡しされるものも、ref修飾子を使えば参照渡しに変更できる。