質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

4回答

8213閲覧

コレクションってrefを付けなくても、普通に参照渡しにならないですか?

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2018/02/18 07:51

編集2018/02/20 15:47

前提・実現したいこと

こちらの記事を見てみると、コレクションは普通は参照渡しにならないみたいなことが書かれていますが、
自分が検証してみた所、refを付けなくても参照渡しのような挙動となりました。
同じコードを書いているはずなのですが、何が違うのでしょうか?
また、上記の記事は間違っていて、コレクションはrefを付けなくても普通に参照渡しになるという認識で大丈夫でしょうか?

試したこと

試したところ、参照渡しのような挙動に見えました。
コレクションは普通に参照渡しですか?

C#

1 void Start () { 2 3 List<string> listNotRef = new List<string>(); 4 listNotRef.Add("1"); 5 DoNotRef(listNotRef); 6 7 foreach(var item in listNotRef){ 8 Debug.Log(item); 9 } 10 11 //1,2と出力される。 12 13 } 14 15 void DoNotRef (List<string> list) { 16 list.Add ("2"); 17 }

追記。

ChangeNoRef()で、l = new List<string>();したとき、何が起こっているのか分かりません。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Sample : MonoBehaviour { 6 7 // Use this for initialization 8 void Start () { 9 var list = new List<string>(); 10 list.Add("Hello"); 11 DebugList (list); //Hello 12 13 Debug.Log("変更します"); 14 ChangeNoRef (list); 15 DebugList (list); //Hello 16 17 18 Debug.Log("変更します"); 19 ChangeRef(ref list); 20 DebugList (list); //World 21 } 22 23 static void ChangeNoRef(List<string> l) 24 { 25 l = new List<string>(); //この行をコメントアウトしたら、Hello, Worldになる。 26 l.Add("World"); 27 } 28 29 30 static void ChangeRef(ref List<string> l) 31 { 32 l = new List<string>(); 33 l.Add("World"); 34 } 35 36 void DebugList(List<string> l){ 37 foreach (string s in l) { 38 Debug.Log (s); 39 } 40 } 41}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

リンク先の内容が間違っています。

C#

1DoNotRef(listRef);

ここは

C#

1DoNotRef(listNotRef);

こうでなくてはなりません。
ref をつけない場合はコレクションでも値渡しになりますが、値渡しでもコレクションの要素の変更はできます。
ですから、要素の変更ができたかどうかを参照渡しであるかどうかの判断材料にするのも間違っています。

次の例をご覧ください。

C#

1using System.Collections; 2using System.Collections.Generic; 3 4var list = new List<string>(); 5list.Add("Hello"); 6foreach (var item in list) 7{ 8 Console.WriteLine(item); 9} 10Console.WriteLine("変更します"); 11Change(ref list); 12foreach (var item in list) 13{ 14 Console.WriteLine(item); 15} 16 17static void Change(ref List<string> l) 18{ 19 l = new List<string>(); 20 l.Add("World"); 21}

C# スクリプト(*.csx)ですから Unity では動きませんが、この出力は次のようになります。

Hello 変更します World

メソッドの中で変数の中身自体を変えています。
これが参照渡しです。

追記

質問の追記を受けて。

C#

1void Start() 2{ 3 string s = "Hello"; 4 Console.WriteLine(s); // Hello 5 ChangeNoRef(s); 6 Console.WriteLine(s); // Hello 7 ChangeRef(ref s); 8 Console.WriteLine(s); // World 9} 10 11void ChangeNoRef(string t) 12{ 13 t = "World"; 14} 15 16void ChangeRef(ref string t) 17{ 18 t = "World"; 19}

この場合、出力は次のようになります。

Hello Hello World

string は List<T> と同じく参照型です。
同じ挙動になると思ってください。

ChangeNoRef() でパラメータである t を書き換えています。
しかし t はメソッド内ローカルであり、もともとの s に影響を及ぼしません。
ですから s を出力した結果は Hello になります。
s の値である string オブジェクト(参照型)を渡しているので値渡しです。

ChangeRef() でも t を書き換えています。
これにより s は t と同じ値である World になります。
s の値ではなく参照を渡しています。
これが参照渡しです。

string は参照型ですが、値型を用いても全く結果は同じです。

投稿2018/02/18 08:29

編集2018/02/18 13:15
Zuishin

総合スコア28656

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/02/18 12:58

ご回答ありがとうございます。 追記にコードを書いて試してみましたが、イメージ的なものがよくわかりません。 可能であれば、何かイメージ的なものを教えていただけませんか?
退会済みユーザー

退会済みユーザー

2018/02/18 15:45

ご回答ありがとうございます。 >ChangeNoRef() でパラメータである t を書き換えています。 >しかし t はメソッド内ローカルであり、もともとの s に影響を及ぼしません。 >ですから s を出力した結果は Hello になります。 >s の値である string オブジェクト(参照型)を渡しているので値渡しです。 ご回答者様がご提示いただいた例では、ChangeNoRef()内の処理が、呼び出し元に影響を受けないことは検証できるのですが、 自分の追記のコードで、下記のようにすると、呼び出し元のリストに影響が起きるのがわからない状況です。そして、下記のコメントアウトを外すと、今度は逆に呼び出し元に影響が起きないという状況になります。 つまり、下記メソッドで、コメントを有効にするか無効にするかで、呼び出し元に影響が起きたり、起きなかったりという動きが理解できていません。 理解力が乏しくて申し訳ないですが、ご教授いただけませんか? static void ChangeNoRef(List<string> l) { //l = new List<string>(); //この行をコメントアウトしたら、Hello, Worldになる。 l.Add("World"); }
Zuishin

2018/02/18 21:40

渡された List オブジェクトのメソッドはもちろん呼び出せます。 そうでなくては渡す意味がありません。 メソッドを呼び出せばその通りに動きます。 これは参照渡しされたからではありません。 値渡しされた参照型オブジェクトのメソッドが呼び出されただけです。 参照渡しと参照型を混同しないでください。 値渡しではメソッドを呼び出すことはできても元々の呼び出し元の変数の値を変えることはできません。 メソッド呼び出しで変数の値が変わったのではなくオブジェクトの状態が変わっただけです。
Zuishin

2018/02/18 22:53

順に整理しましょう。 読み飛ばさず順に理解して進んでください。 (1) 値型(int, char など)の変数にはオブジェクトそのものが入っています。 参照型(string, List など)の変数にはオブジェクトへの参照が入っています。 (2) 値渡しとは変数の値をメソッドに渡すことで、参照渡しとは変数への参照をメソッドに渡すことです。 (3) 値型を値渡しした時にはオブジェクトそのものがコピーして渡されます。 (4) 値型を参照渡しした時には変数への参照つまりオブジェクトへの参照が渡されます。 (5) 参照型を値渡しした時には変数の値つまりオブジェクトへの参照がコピーして渡されます。 オブジェクトを基準に見ると参照を渡していますがこれは参照渡しではありません。変数を基準に見ると値が渡されているからです。 (6) 参照型を参照渡しした時には変数への参照つまりオブジェクトへの参照の参照が渡されます。 (7) 質問のリンク先はバグの入った間違ったソースを元に検証しているので間違った結論が出ています。忘れてください。 (8) 参照型を値渡しした時には変数の値がコピーして渡されているので変数の値そのもの(オブジェクトへの参照)をメソッド内で変えることはできません。 ただし値はオブジェクトのコピーではなくオブジェクトへの参照のコピーなので元のオブジェクトのメソッドを呼び出したりプロパティを変更したりはできます。 これは List に限らずすべての参照型でそのようになります。 (9) 参照型を参照渡しした場合にはメソッド呼び出しやプロパティの変更に加えて変数の値そのものも変更できます。 (10) 以上より元のオブジェクトのプロパティを変更できてもそれは参照渡しの証拠にはなりません。 元の変数の値を変更できるかどうかで判断してください。
退会済みユーザー

退会済みユーザー

2018/02/19 14:28 編集

ご回答ありがとうございます。 詳しいご解説ありがとうございます。 かなり理解できました。 また、2点疑問が湧いたのですが質問させていただけますか? ・参照型の値渡しと参照型の参照渡しで、挙動に差異が出るのは、オブジェクトをnewしたときぐらいかと思うのですが、いかがですか? (参照型の値渡しでオブジェクトをnewすると、元のオブジェクトとは別のオブジェクトをnewし、 参照型の参照渡しでオブジェクトをnewすると、元のオブジェクトを作り直してnewすると認識しています。) ・「参照変数A→オブジェクトA」の参照型を参照渡しすると、「参照変数B→参照変数A→オブジェクトA」のように、参照渡しされた参照変数Bは、オブジェクトAを参照するのに、途中で参照変数Aを経由する形になるかと思うのですが、この参照変数Bでオブジェクトを参照するのと、参照変数Aでオブジェクトを参照するのと違いってどんなものがありますか? 思いつく限り、「参照変数Bが消えても参照変数Aはオブジェクトの参照を続けている」と「参照変数Aが消えると参照変数Bもオブジェクトの参照ができなくなる」ぐらいかと思いました。
Zuishin

2018/02/19 14:32

ちょっと前提がいろいろ違います。 参照渡しは「変数」(オブジェクトでなく)の参照を渡すものと単純に考えてください。
退会済みユーザー

退会済みユーザー

2018/02/20 15:45

ご回答ありがとうございます。 前回の自分のコメントが間違っていたことがわかりました。 ご教授いただきありがとうございました。
guest

0

質問のコードでの動作は値渡し(call by value)の一種である共有渡し(call by sharing、「オブジェクト渡し」や「参照の値渡し」とも言われる)と言われるものです。refキーワードを使用した場合の動作である参照渡し(call by reference)とは異なる動作になります。

なぜ、そのような動作になるのかを理解するには、Listが参照型であるということ、そして、C#の参照型はどのように変数に格納されているのかと言うことを理解できなくてはなりません。参照型の場合、変数に入っているのはそのデータそのものではなく、そのデータの実体を参照する何か(参考文献に載せているMSDNのドキュメントでは「参照(reference)」と書かれています、同様の仕組みがあるJavaでは「参照値(reference value)」と呼ばれているため、一般的に「参照値」と言われる場合が多いです)です。この何かを値渡しするのが、共有渡し(なので、「参照の値渡し」とも言われるますが、C#やJava以外の言語も考えると、実装上は「参照の値」というものではない場合もありえるため、適切かどうかは微妙なところです)になります。

注意して欲しいのは、参照渡しと参照型は全く異なる概念であるということです。ref (C# リファレンス) | Microsoft Docsにも混同することがないように注意事項がわざわざ書いてあります。

参考文献

投稿2018/02/18 09:23

編集2018/02/18 09:27
raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/02/18 12:57

ご回答ありがとうございます。 追記にコードを書いて試してみましたが、イメージ的なものがよくわかりません。 可能であれば、何かイメージ的なものを教えていただけませんか?
guest

0

こんにちは。

リンク先はZuishinさんが回答されているように、バグってますね。

次に、参照型変数をrefを付けて参照渡しするという考え方は、実はかなりハードです。
C#の参照型変数はオブジェクトをポイントしています。その参照型変数自体を参照渡しするので参照型変数がポイントしているオブジェクトを別のオブジェクトへ切り替えることができるような渡し方です。

参照型変数を普通に渡すと、文法上は「値渡し」ですが、意味的には「参照渡し」となります。
ポイントしているオブジェクトのアドレスを渡しているようなものですから。

投稿2018/02/18 09:03

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/02/18 12:57

ご回答ありがとうございます。 追記にコードを書いて試してみましたが、イメージ的なものがよくわかりません。 可能であれば、何かイメージ的なものを教えていただけませんか?
Chironian

2018/02/18 13:46

図は合っていると言って良いと思います。 Hello と World の四角は、それぞれが List<String>型のオブジェクトですね。
guest

0

記事が間違っています。

http://ufcpp.net/study/csharp/oo_reference.html
ここら辺を一度読んでください。

C#は通常値渡しで、refを書いた場合参照渡しになります

Listはクラスなので、記事でいう所の参照型になります。
参照型を値渡しするというのは、実態への参照が値渡しで渡されます。
要するに、関数を呼ぶ前と後で同じ実態を参照したいという事であれば参照型の場合refは必要ありません。

投稿2018/02/18 08:38

編集2018/02/18 09:03
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2018/02/18 08:41

それは値型と参照型についての説明です。 値渡しと参照渡しはそれらとは全く別物です。
Zuishin

2018/02/18 08:44

また記事の間違いはバグの混入によるものですから論点がずれます。
退会済みユーザー

退会済みユーザー

2018/02/18 09:05

ご指摘ありがとうございます。 説明を追記してみました。間違いなどあれば教えていただけるとありがたいです。
退会済みユーザー

退会済みユーザー

2018/02/18 12:57

ご回答ありがとうございます。 追記にコードを書いて試してみましたが、イメージ的なものがよくわかりません。 可能であれば、何かイメージ的なものを教えていただけませんか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問