🗿
[C#] 参照型を値渡ししたときの不思議な動作を調べる
もくじ
やりたいこと
下記のようなコードがあったときに、コード中のEditList()
メソッドに渡したlistが、
- EditList()の中でAddした値は、呼び出し元のlistにも反映されている
- が、EditList()の中でOrderByした結果は、呼び出し元のlistに反映されていない
ということがあった。
なんでそんなことになるのか?が全然わからなかったので調べた。その時のメモ。
その時のコード要点.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
var list = new List<int>() { 2, 5, 1, -2 };
EditList(list, 3);
list.ForEach(x => Debug.WriteLine(x));
}
private void EditList(List<int> list, int val)
{
list.Add(val);
list = list.OrderBy(x => x).ToList();
}
調べた結果
下記のページに、答えがすべてあった。
補助情報として、
こちらのページを自分なりに整理すると、
- 型には、2つのタイプがある。
- 値型
- 参照型
- 値の受け渡しには2つある。
- 値渡し
- 参照渡し
- それらの組み合わせで、いろんな型の値をメソッドに受け渡す時には、下記の4パターンある
- 値型の値渡し
- 値型の参照渡し
- 参照型の値渡し
- 参照型の参照渡し
で、今回上のコードではまったのは、3.の 「参照型の値渡し」 によって、不思議な動きをしていたっぽい。
下のサンプルコードで実験してみた。
サンプルコード
参照型を値渡し
//********************************************************
//* 参照型 を 値渡し するパターン
//* (List<T>が参照型)
//********************************************************
private void Button_Click(object sender, RoutedEventArgs e)
{
var list = new List<int>() { 2, 5, 1, -2 };
// ①この時点で list は {2, 5, 1, -2 }
EditList(list, 3);
// ④この時点で list は {2, 5, 1, -2, 3 }
// ※③で値渡しされた参照を書き換えても外のlistには反映されない
}
private void EditList(List<int> list, int val)
{
list.Add(val);
// ②この時点で list は {2, 5, 1, -2, 3 }
// ここのlistと外のlistはこの時点では同じヒープ上のメモリを指している
list = list.OrderBy(x => x).ToList();
// ↑ここのlistと外のlistが別のメモリを指すようになった
// ③この時点で list は {-2, 1, 2, 3, 5} でソートされてるが、
// 値渡しされたlistの参照先は、呼び出し元のlist(参照型)には反映されない
}
参照型を参照渡し
//********************************************************
//* 参照型 を 参照渡し するパターン
//* (List<T>が参照型)
//********************************************************
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var list = new List<int>() { 2, 5, 1, -2 };
// ①この時点で list は {2, 5, 1, -2 }
EditListRef(ref list, 3);
// ④この時点で list は {-2, 1, 2, 3, 5}
// ※③で代入された参照が、外にも反映される
}
private void EditListRef(ref List<int> list, int val)
{
list.Add(val);
// ②この時点で list は {2, 5, 1, -2, 3 }
// ここのlistと外のlistはこの時点では同じヒープ上のメモリを指している
list = list.OrderBy(x => x).ToList();
// ↑ここのlistと外のlistが別のメモリを指すようになったが、
// 参照渡しされたlistに代入されたその参照は、外のlistにも反映される
// ③この時点で list は {-2, 1, 2, 3, 5}
}
結果
(参考にさせて頂いたページと似た図になり申し訳ないですが、)
上のコードで試した結果、下記の理解をした。
参照型を値渡ししたときのイメージ
元のlistを指しているうちにAddされた値は反映される。
その後OrderByで新しい領域が確保され、そこへの参照がlistに入れられる。
が、listは値渡しされたものであるので、引数の渡し元へは反映されない。
参照型を参照渡ししたときのイメージ
値渡しのときと同様、OrderByで新しい領域が確保され、そこへの参照がlistに入れられる。
listは参照渡しされたものであるので、引数の渡し元へもそれが反映される。
なので、この理解で行くと、
一言で「呼び出し元のlistにOrderByの結果が反映された」といっても、
元々ヒープ上に確保されたlistの領域がOrderByで並び替えられたわけではなく、
元々ヒープ上に確保されたlistのデータを使ってOrderByしたリストを別のヒープ上に確保して、それを呼び出し元が見るようになった、ということになる。
考察
上記のような考え方で、自分としてはしっくり来てしまったが、本当に正しいのかどうか、少々自信がない。もし間違いなどお気づきの際は、コメント頂けると大変助かります。
参考
Discussion