オブジェクトを渡すときに「ref」キーワードを使用するのはなぜですか?


290

オブジェクトをメソッドに渡す場合、なぜrefキーワードを使用する必要があるのですか?とにかく、これはデフォルトの動作ではありませんか?

例えば:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

出力は "Bar"で、オブジェクトが参照として渡されたことを意味します。

回答:


298

refオブジェクトを変更したい場合はaを渡します:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

DoSomethingを呼び出した後t、オリジナルを参照しないnew TestRefが、完全に異なるオブジェクトをます。

これは、たとえば、不変オブジェクトの値を変更する場合にも役立ちますstringstring一度作成されたoneの値は変更できません。しかし、を使用するrefことにより、異なる値を持つ別の文字列に文字列を変更する関数を作成できます。

編集:他の人が述べたように。ref必要な場合以外は使用しないでください。を使用refすると、メソッドに自由に引数を変更してもらうことができます。メソッドの呼び出し元は、この可能性を処理できるようにコード化する必要があります。

また、パラメータタイプがオブジェクトの場合、オブジェクト変数は常にオブジェクトへの参照として機能します。つまり、refキーワードを使用すると、参照への参照が取得されます。これにより、上記の例で説明したように作業を行うことができます。ただし、パラメーターの型がプリミティブ値(例int:)の場合、このパラメーターがメソッド内で割り当てられていると、メソッドが戻った後に、渡された引数の値が変更されます。

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

88

「値による参照の受け渡し」と「参照によるパラメータ/引数の受け渡し」を区別する必要があります。

これがニュースグループに登場するたびに注意深く書く必要がないように、私はこのテーマについてかなり長い記事を書きました:)


1
VB6を.Net C#コードにアップグレードしているときに問題が発生しました。ref、out、およびplainパラメータを取る関数/メソッドシグネチャがあります。では、単純なパラメータと参照の違いをどのように区別できるでしょうか?
bonCodigo 2015年

2
@bonCodigo:「よりよく区別する」という意味がわからない-署名の一部でありref、呼び出しサイトでも指定する必要があります...他にどこを区別したいですか?セマンティクスもかなり明確ですが、注意深く表現する必要があります(一般的な過度に単純化された「オブジェクトは参照渡し」ではなく)。
Jon Skeet

なぜビジュアルスタジオがまだ渡されたものを明示的に表示しないのか分かりません
MonsterMMORPG 2017

3
@MonsterMMORPG:どういう意味かわかりません。
ジョン・スキート

56

.NETでは、パラメーターをメソッドに渡すと、コピーが作成されます。値型では、値に加えた変更はメソッドスコープで行われ、メソッドを終了すると失われます。

参照タイプを渡すと、コピーも作成されますが、これは参照のコピーです。つまり、同じオブジェクトへのメモリ内に2つの参照があります。したがって、参照を使用してオブジェクトを変更すると、オブジェクトが変更されます。ただし、参照自体を変更する場合-コピーであることを忘れないでください-メソッドを終了すると、変更も失われます。

人々が以前に言ったように、割り当ては参照の修正なので、失われます:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

上記のメソッドは、元のオブジェクトを変更しません。

あなたの例の少しの修正

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

6
私はこの答えが受け入れられた答えよりも好きです。refキーワードで参照型変数を渡すときに何が起こっているかをより明確に説明します。ありがとうございました!
ステファン

17

TestRefはクラス(参照オブジェクト)であるため、refとして渡さなくてもtの内容を変更できます。ただし、tを参照として渡すと、TestRefは元のtの参照を変更できます。つまり、別のオブジェクトを指すようにします。


16

refあなたが書くことができます。

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

そして、tはメソッドが完了した後に変更されます。


8

foo参照型(例:)の変数(例:)は、「オブジェクト#24601」の形式のオブジェクト識別子List<T>保持していると考えてください。ステートメントが"Object#24601"(4つのアイテムのリスト)を保持するとします。次に、呼び出しはオブジェクト#24601に長さを要求し、4と応答するため、4 と等しくなります。foo = new List<int> {1,5,7,9};foofoo.Lengthfoo.Length

fooを使用せずにメソッドに渡された場合ref、そのメソッドはオブジェクト#24601を変更する可能性があります。このような変更の結果として、foo.Length4に等しくなくなる可能性があります。ただし、メソッド自体はを変更できなくなりfoo、「オブジェクト#24601」を保持し続けます。

パラメータfooとして渡すとref、呼び出されたメソッドはオブジェクト#24601だけでなく、fooそれ自体にも変更を加えることができます。このメソッドは、新しいオブジェクト#8675309を作成し、その参照をに保存しますfoo。もしそうなら、foo、「オブジェクト#24601」ではなく「オブジェクト#8675309」が保持されます。

実際には、参照型の変数は "Object#8675309"という形式の文字列を保持しません。意味のある数に変換できるものは何も保持していません。各参照型変数はビットパターンを保持しますが、そのような変数に格納されているビットパターンとそれらが識別するオブジェクトとの間に固定の関係はありません。コードがオブジェクトまたはオブジェクトへの参照から情報を抽出し、後で別の参照が同じオブジェクトを識別したかどうかを判断する方法はありません。ただし、コードが元のオブジェクトを識別する参照を保持または知っている場合を除きます。


5

これは、ポインターをCのポインターに渡すようなものです。.NETでは、元のTが参照しているものを個人的に変更できます。


3

ref参照タイプでキーワードを使用すると、参照を参照に効果的に渡すことができます。多くの点でoutキーワードを使用するのと同じですが、メソッドが実際にref「ed」パラメーターに何かを割り当てるという保証がないという小さな違いがあります。


3

ref 2つのスコープのみのグローバル領域として模倣(または動作)します。

  • 発信者
  • 呼び出し先。

1

ただし、値を渡す場合は状況が異なります。参照によって値を強制的に渡すことができます。これにより、たとえば、整数をメソッドに渡し、メソッドに代わって整数を変更させることができます。


4
参照を渡す場合でも、値タイプの値を渡す場合でも、デフォルトの動作は値渡しです。参照型で、渡す値が参照であることを理解する必要あります。これは参照渡しと同じではありません。
Jon Skeet、

1

Refは、関数がオブジェクト自体にアクセスできるか、値にのみアクセスできるかを示します。

参照渡しは言語に拘束されません。これは、値渡し、名前渡し、ニーズ渡しなどの次のパラメーターバインディング戦略です...

補足:クラス名TestRefは、このコンテキストではひどく悪い選択です;)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.