7

C#のstring型は参照型であり、参照型の引数を値で渡すと参照がコピーされるため、ref修飾子を使用する必要はありません。ただし、ref入力を変更するには修飾子を使用する必要がありますstring。どうしてこれなの?

using System;

class TestIt
{
    static void Function(ref string input)
    {
        input = "modified";
    }

    static void Function2(int[] val) // Don't need ref for reference type
    {
        val[0] = 100;
    }

    static void Main()
    {
        string input = "original";
        Console.WriteLine(input);
        Function(ref input);      // Need ref to modify the input
        Console.WriteLine(input);

        int[] val = new int[10];
        val[0] = 1;
        Function2(val);
        Console.WriteLine(val[0]);
    }
}
4

7 に答える 7

14

文字列パラメーターを参照する必要がある理由は、文字列オブジェクトへの参照を渡しても、パラメーターに別のものを割り当てると、現在パラメーター変数に格納されている参照が置き換えられるだけだからです。つまり、パラメーターが参照するものを変更しましたが、元のオブジェクトは変更されていません。

パラメータを参照すると、パラメータが実際には渡された変数のエイリアスであることを関数に伝えたので、それに割り当てると目的の効果が得られます。

EDIT : string は不変の参照型ですが、ここではあまり関係がないことに注意してください。新しいオブジェクト (この場合は "変更された" 文字列オブジェクト) を割り当てようとしているだけなので、このアプローチはどの参照型でも機能しません。たとえば、次のコードへのわずかな変更を検討してください。

using System;

class TestIt 
{
    static void Function(ref string input)
    {
        input = "modified";
    }

    static void Function2(int[] val) // don't need ref for reference type
    {
        val = new int[10];  // Change: create and assign a new array to the parameter variable
        val[0] = 100;
    }
    static void Main()
    {
        string input = "original";
        Console.WriteLine(input);
        Function(ref input);      // need ref to modify the input
        Console.WriteLine(input);

        int[] val = new int[10];
        val[0] = 1;
        Function2(val);
        Console.WriteLine(val[0]); // This line still prints 1, not 100!
    }
}

ここで、非参照パラメーター変数に新しいオブジェクトを割り当てているため、配列テストは「失敗」します。

于 2011-05-29T05:51:37.130 に答える
6

string似ているが変更可能なタイプと比較すると役立ちますstring。の短い例を見てみましょうStringBuilder:

public void Caller1()
{
    var builder = new StringBuilder("input");
    Console.WriteLine("Before: {0}", builder.ToString());
    ChangeBuilder(builder);
    Console.WriteLine("After: {0}", builder.ToString());
}

public void ChangeBuilder(StringBuilder builder)
{
    builder.Clear();
    builder.Append("output");
}

これにより、次が生成されます。

Before: input
After: output

したがって、変更可能な型、つまり値を変更できる型の場合、その型への参照をメソッドのように渡してChangeBuilder使用しないrefout、呼び出した後に値を変更したままにすることが可能であることがわかります。

builderまた、 で実際に別の値を設定したことは一度もないことに注意してChangeBuilderください。

対照的に、文字列で同じことを行うと、次のようになります。

public void Caller2()
{
    var s = "input";
    Console.WriteLine("Before: {0}", s);
    TryToChangeString(s);
    Console.WriteLine("After: {0}", s);
}

public void TryToChangeString(string s)
{
    s = "output";
}

これにより、次が生成されます。

Before: input
After: input

なんで?TryToChangeStringでは、変数によって参照される文字列の内容を実際に変更していないため、まったく新しい文字列に置き換えていsます sさらに、sはローカル変数であるため、関数TryToChangeStringで の値を置き換えても、関数呼び出しに渡された変数には影響しません。s

astringimmutablerefであるため、 orを使用しない限り、呼び出し元の文字列に影響outを与える方法はありません。

最後に、最後の例は私たちが望むことを行いstringます:

public void Caller3()
{
    var s = "input";
    Console.WriteLine("Before: {0}", s);
    ChangeString(ref s);
    Console.WriteLine("After: {0}", s);
}

public void ChangeString(ref string s)
{
    s = "output";
}

これにより、次が生成されます。

Before: input
After: output

このrefパラメーターは、実際には 2 つのs変数を互いのエイリアスにします。あたかもそれらが同じ変数であるかのようです。

于 2011-05-29T06:18:53.903 に答える
5

文字列は不変です。文字列を変更するのではなく、参照先のオブジェクトを別のものに置き換えます。

それをたとえばリストと比較してください。アイテムを追加するには、参照は必要ありません。リスト全体を別のオブジェクトに置き換えるには、ref (または out) が必要です。

于 2011-05-29T05:49:17.617 に答える
3

これは、すべての不変型に当てはまります。stringたまたま不変です。

メソッドの外で不変型を変更するには、参照を変更する必要があります。したがって、 or のいずれrefoutがメソッドの外部に影響を与える必要があります。

注:あなたの例では、他の例と一致しない特定のケースを呼び出していることに注意してください。実際には、既存の参照を単に変更するのではなく、別の参照を指しています。dlev (および私のコメントで Skeet 自身) が指摘したように、変更可能なものを含む他のすべての型 (例: val = new int[1]) に対して同じことを行った場合、メソッドが返されると変更が「失われる」ことになります。上記のように使用refまたは使用しない限り、メモリ内の同じオブジェクト。outstring

うまくいけば明確にするために:

メモリ内のオブジェクトを指すポインタを渡しています。refまたはを指定しないoutと、まったく同じ場所を指す新しいポインターが作成され、コピーされたポインターを使用してすべての変更が行われます。それらを使用すると、同じポインターが使用され、ポインターに加えられたすべての変更がメソッドの外部に反映されます。

オブジェクトが変更可能な場合、それはオブジェクトの新しいインスタンスを作成せずに変更できることを意味します。新しいインスタンスを作成する場合は、メモリ内の別の場所を指す必要があります。つまり、ポインターを変更する必要があります。

オブジェクトが不変である場合、それは新しいインスタンスを作成しないと変更できないことを意味します。

この例では、string(equal to "modified") の新しいインスタンスを作成し、ポインタ ( input) をその新しいインスタンスを指すように変更しました。int配列 については、 が指す 10 個の値の 1 つを効果的に変更しましたval。これは、 のポインターをいじる必要はありませんval。目的の場所 (配列の最初の要素) に移動し、その最初の値を変更するだけです。所定の位置に。

より類似した例は次のようになります (dlev から盗用されましたが、これはそれらを真に比較できるようにする方法です):

static void Function(ref string input)
{
    input = "modified";
}

static void Function2(int[] val)
{
    val = new int[1];
    val[0] = 100;
}

どちらの関数も、パラメーターのポインターを変更します。使用refしたからinputといって、その変更を「記憶」します。ポインターを変更すると、渡されたポインターが変更され、単なるコピーではないためです。

valintは、関数の外では 10 の配列であり、val[0]1 のままです。これは、内部の " val"Function2が、もともと Main と同じ場所を指す別のvalポインターであるためです。ただし、新しい配列が作成された後は別の場所を指します (別のポインターは新しい配列を指し、元のポインターは引き続き同じ場所を指します)。

配列で使用refした場合int、それは変更されたはずです。そして、サイズも変わっていたでしょう。

于 2011-05-29T05:49:31.620 に答える
0

あなたが正しい。配列と文字列は参照型です。しかし、正直に言って、同様の動作を比較すると、次のように書く必要があります。

static void Function2(int[] val) // It doesn't need 'ref' for a reference type
{
    val = new[] { 1, 2, 3, 4 };
}

しかし、あなたの例では、 reference を介して C# 1 次元配列のいくつかの要素で書き込み操作を実行しますval

于 2015-07-23T21:22:58.263 に答える
0

混乱は、参照型参照がデフォルトで値渡しされることです。参照自体 (オブジェクトが指すもの) を変更するには、参照を使用して参照を渡す必要がありますref

あなたの場合、文字列を処理しています-変数に文字列を割り当てる(または追加するなど)と、参照が変更されます。文字列は不変であるため、これを回避する方法もないため、ref.

于 2011-05-29T05:48:44.117 に答える