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

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

ただいまの
回答率

88.81%

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

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 14K+

tkmnusr

score 319

 前提・実現したいこと

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

 試したこと

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

    void Start () {

        List<string> listNotRef = new List<string>();
        listNotRef.Add("1"); 
        DoNotRef(listNotRef);

        foreach(var item in listNotRef){
            Debug.Log(item);
        }

        //1,2と出力される。

    }

    void DoNotRef (List<string> list) {
        list.Add ("2"); 
    }

 追記。

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Sample : MonoBehaviour {

    // Use this for initialization
    void Start () {
        var list = new List<string>();
        list.Add("Hello");
        DebugList (list);  //Hello

        Debug.Log("変更します");
        ChangeNoRef (list);
        DebugList (list);  //Hello


        Debug.Log("変更します");
        ChangeRef(ref list);
        DebugList (list);  //World
    }

    static void ChangeNoRef(List<string> l)
    {
        l = new List<string>();  //この行をコメントアウトしたら、Hello, Worldになる。
        l.Add("World");
    }


    static void ChangeRef(ref List<string> l)
    {
        l = new List<string>();
        l.Add("World");
    }

    void DebugList(List<string> l){
        foreach (string s in l) {
            Debug.Log (s);
        }
    }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+3

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

DoNotRef(listRef);

ここは

DoNotRef(listNotRef);

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

次の例をご覧ください。

using System.Collections;
using System.Collections.Generic;

var list = new List<string>();
list.Add("Hello");
foreach (var item in list)
{
    Console.WriteLine(item);
}
Console.WriteLine("変更します");
Change(ref list);
foreach (var item in list)
{
    Console.WriteLine(item);
}

static void Change(ref List<string> l)
{
    l = new List<string>();
    l.Add("World");
}

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

Hello
変更します
World

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

 追記

質問の追記を受けて。

void Start()
{
    string s = "Hello";
    Console.WriteLine(s); // Hello
    ChangeNoRef(s);
    Console.WriteLine(s); // Hello
    ChangeRef(ref s);
    Console.WriteLine(s); // World
}

void ChangeNoRef(string t)
{
    t = "World";
}

void ChangeRef(ref string t)
{
    t = "World";
}

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

Hello
Hello
World

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/19 23:28 編集

    ご回答ありがとうございます。
    詳しいご解説ありがとうございます。
    かなり理解できました。
    また、2点疑問が湧いたのですが質問させていただけますか?

    ・参照型の値渡しと参照型の参照渡しで、挙動に差異が出るのは、オブジェクトをnewしたときぐらいかと思うのですが、いかがですか?
    (参照型の値渡しでオブジェクトをnewすると、元のオブジェクトとは別のオブジェクトをnewし、
    参照型の参照渡しでオブジェクトをnewすると、元のオブジェクトを作り直してnewすると認識しています。)

    ・「参照変数A→オブジェクトA」の参照型を参照渡しすると、「参照変数B→参照変数A→オブジェクトA」のように、参照渡しされた参照変数Bは、オブジェクトAを参照するのに、途中で参照変数Aを経由する形になるかと思うのですが、この参照変数Bでオブジェクトを参照するのと、参照変数Aでオブジェクトを参照するのと違いってどんなものがありますか?
    思いつく限り、「参照変数Bが消えても参照変数Aはオブジェクトの参照を続けている」と「参照変数Aが消えると参照変数Bもオブジェクトの参照ができなくなる」ぐらいかと思いました。

    キャンセル

  • 2018/02/19 23:32

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

    キャンセル

  • 2018/02/21 00:45

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

    キャンセル

+2

質問のコードでの動作は値渡し(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 21:57

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

    キャンセル

0

記事が間違っています。

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/18 17:41

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

    キャンセル

  • 2018/02/18 17:44

    また記事の間違いはバグの混入によるものですから論点がずれます。

    キャンセル

  • 2018/02/18 18:05

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

    キャンセル

  • 2018/02/18 21:57

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

    キャンセル

0

こんにちは。

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/18 21:57

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

    キャンセル

  • 2018/02/18 22:46

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

    キャンセル

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

  • ただいまの回答率 88.81%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る