web-dev-qa-db-ja.com

C#の参照型変数の「ref」の使用は何ですか?

値型(intstructなど)をパラメーターとして(refキーワードなしで)渡すと、その変数のコピーが渡されることを理解していますメソッドに。ただし、refキーワードを使用すると、新しい変数ではなく、その変数への参照が渡されます。

ただし、クラスのような参照タイプでは、refキーワードがなくても、参照はコピーではなくメソッドに渡されます。では、参照タイプでrefキーワードを使用するのは何ですか?


例:

var x = new Foo();

次の違いは何ですか?

void Bar(Foo y) {
    y.Name = "2";
}

そして

void Bar(ref Foo y) {
    y.Name = "2";
}
164
Andreas Grech

fooが指すものをyを使用して変更できます。

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"
148
user7116

実際のreferenceを変更する場合がありますが、指すオブジェクトではありません:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);
29
Mehrdad Afshari

Jon Skeetは、C#でのパラメーターの受け渡しについて すばらしい記事 を書きました。値、参照(ref)、および出力(out)によるパラメーターの受け渡しの正確な動作と使用法を明確に詳述しています。

refパラメーターに関連するそのページからの重要な引用を以下に示します。

参照パラメーターは、関数メンバーの呼び出しで使用される変数の値を渡しません。変数自体を使用します。関数メンバー宣言で変数の新しい保存場所を作成するのではなく、同じ保存場所が使用されるため、関数メンバーの変数の値と参照パラメーターの値は常に同じになります。参照パラメーターには、宣言と呼び出しの両方の一部としてref修飾子が必要です。つまり、参照によって何かを渡すときは常に明確です。

17
Noldorin

ここで非常にうまく説明されています: http://msdn.Microsoft.com/en-us/library/s6938f28.aspx

記事の要約:

参照型の変数には、そのデータが直接含まれていません。データへの参照が含まれています。参照型パラメーターを値で渡すと、クラスメンバの値など、参照が指すデータを変更できます。ただし、参照自体の値を変更することはできません。つまり、同じ参照を使用して新しいクラスにメモリを割り当てて、ブロックの外部に保持することはできません。そのためには、refキーワードまたはoutキーワードを使用してパラメーターを渡します。

15

Refキーワードを使用して参照型を渡すと、参照ごとに参照が渡され、呼び出すメソッドはパラメーターに新しい値を割り当てることができます。その変更は呼び出し元のスコープに伝播します。 refがない場合、参照は値によって渡され、これは起こりません。

C#にはrefによく似た「out」キーワードもありますが、「ref」ではメソッドを呼び出す前に引数を初期化する必要があり、「out」では受信メソッドに値を割り当てる必要があります。

9
Rytmis

渡された参照を変更できます。

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

渡された参照を気にしない場合は、outを使用することもできます。

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}
5
HVS

コードの別の束

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}
4
Svend

既存の回答に加えて:

2つの方法の違いを求めたとき:refまたはoutを使用する場合、co(ntra)varianceはありません。

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}
3
springy76

メソッドのパラメーターは常にコピーを渡しているようです。質問は何のコピーです。コピーはオブジェクトのコピーコンストラクターによって実行され、すべての変数はC#のオブジェクトであるため、すべての変数に当てはまると思います。変数(オブジェクト)は、ある住所に住んでいる人のようなものです。これらの住所に住んでいる人々を変更するか、電話帳でそれらの住所に住んでいる人々への参照を作成できます(浅いコピーを作成します)。したがって、複数の識別子が同じアドレスを参照できます。参照タイプはより多くのスペースを必要とするため、スタック内の識別子に矢印で直接接続されている値タイプとは異なり、ヒープ内の別のアドレスに値があります(より大きなスペースがあります)。このスペースは、ヒープから取得する必要があります。

値タイプ:識別子(値=スタック値のアドレスを含む)---->値タイプの値

参照タイプ:識別子(値=スタック値のアドレスを含む)---->(値=ヒープ値のアドレスを含む)---->ヒープ値(ほとんどの場合、他の値へのアドレスを含む) Array [0]、Array [1]、array [2]への方向

値を変更する唯一の方法は、矢印に従うことです。値に到達できない方法で1つの矢印が失われたり変更されたりした場合。

1
Petar Drianov