C#の参照型の値渡しに気をつけろ
背景
C++のコード
#include <iostream> #include <vector> #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; using ll = long long; using p = pair<int, int>; void foo(vector<int> A) { if (A.size() == 10) { return; } A.push_back(1); foo(A); // print rep(i, A.size()) { cout << A[i]; } cout << endl; } int main() { foo(vector<int>(1, 1)); }
出力結果
1111111111 111111111 11111111 1111111 111111 11111 1111 111 11
C#のコード
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main(string[] args) { Program obj = new Program(); obj.Foo(new List<int> { 1 }); } void Foo(List<int> A) { if (A.Count == 10) { return; } A.Add(1); this.Foo(A); // print foreach (var e in A) { Console.Write(e); } Console.WriteLine(); } }
出力結果
1111111111 1111111111 1111111111 1111111111 1111111111 1111111111 1111111111 1111111111 1111111111
!?
Aの値が更新されてしまっている
改善する
- どうやらListは参照型らしい
- refをつけなければ値渡しになる
- つまり参照型の値渡しなのでオブジェクトを渡すときと同じ挙動になる
- したがって、渡された値まるごと置き換えた場合は値がコピーされるが(値渡し)、中身の一部を変えた場合(配列に要素追加するなど)は呼び出し元も変更されてしまう(共通のレジスタを参照)
- ということで渡された値まるごと置き換えて値渡しにしてあげればよい
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main(string[] args) { Program obj = new Program(); obj.Foo(new List<int> { 1 }); } void Foo(List<int> A) { var B = new List<int>(A); if (B.Count == 10) { return; } B.Add(1); this.Foo(B); // print foreach (var e in A) { Console.Write(e); } Console.WriteLine(); } }
出力結果
1111111111 111111111 11111111 1111111 111111 11111 1111 111 11
まとめ
C#
- Listは参照型
- 引数に渡すときはrefを付けない限り値渡しで渡される
- 参照型の値渡しはオブジェクトを渡すときと同じ挙動になる
C++
- vectorは値型
- &を付けない限り値渡しで渡される