web-dev-qa-db-ja.com

C#文字列参照型?

C#の「文字列」は参照型であることを知っています。これはMSDNにあります。ただし、このコードは次のように機能しません。

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

文字列をパラメーターとして渡し、参照型であるため、出力は「渡す前」「渡す後」である必要があります。2番目の出力ステートメントは、TestIメソッドでテキストが変更されたことを認識する必要があります。しかし、「渡す前」「渡す前」を取得するので、参照ではなく値で渡されるように見えます。文字列は不変であることは理解していますが、ここで何が起こっているのかを説明する方法がわかりません。私は何が欠けていますか?ありがとう。

154
CriosR

文字列への参照は値で渡されます。参照を値で渡すこととオブジェクトを参照で渡すことには大きな違いがあります。両方のケースで「参照」という単語が使用されているのは残念です。

do文字列参照by参照を渡すと、次のように動作しますあなたが期待する:

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

ここで、参照が参照するオブジェクトを変更することと、変数(パラメーターなど)を変更して別のオブジェクトを参照することを区別する必要があります。文字列は不変であるため、文字列を変更することはできませんが、代わりにStringBuilderを使用してそれを示すことができます。

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

詳細については、 パラメータの受け渡しに関する私の記事 を参照してください。

203
Jon Skeet

質問に答える必要がある場合:文字列は参照型であり、参照として動作します。実際の文字列ではなく、参照を保持するパラメーターを渡します。問題は関数にあります:

public static void TestI(string test)
{
    test = "after passing";
}

パラメータtestは文字列への参照を保持しますが、コピーです。文字列を指す2つの変数があります。また、文字列を使用した操作では実際に新しいオブジェクトが作成されるため、新しい文字列を指すようにローカルコピーを作成します。ただし、元のtest変数は変更されません。

ref変数の値を渡すのではなく、単に参照を渡すため、関数宣言と呼び出し作業にtestを配置するための推奨ソリューション。したがって、関数内の変更はすべて元の変数を反映します。

最後に繰り返します。文字列は参照型ですが、その不変の行test = "after passing";は実際に新しいオブジェクトを作成し、変数testのコピーは新しい文字列を指すように変更されます。

30
Martin Dimitrov

他の人が述べたように、.NETのString型は不変であり、その参照は値によって渡されます。

元のコードでは、この行が実行されるとすぐに:

test = "after passing";

testは、元のオブジェクトを参照しなくなりました。 newStringオブジェクトを作成し、管理対象ヒープ上のそのオブジェクトを参照するtestを割り当てました。

目に見える正式なコンストラクターがないため、多くの人がここでつまずいているように感じます。この場合、String型はその構築方法で言語をサポートしているため、舞台裏で発生しています。

したがって、これがtestへの変更がTestI(string)メソッドのスコープ外で表示されない理由です。値による参照を渡し、その値が変更されました!ただし、String参照が参照によって渡された場合、参照が変更されたときに、TestI(string)メソッドのスコープ外に表示されます。

この場合、refまたはoutキーワードが必要です。 out キーワードは、この特定の状況に少し適していると感じています。

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}
23
Derek W

実際には、それはどのオブジェクトでも同じでした。つまり、参照型であり、参照渡しはc#の2つの異なることです。

これは機能しますが、タイプに関係なく適用されます。

public static void TestI(ref string test)

文字列が参照型であることについても、特別なものです。不変に設計されているため、すべてのメソッドはインスタンスを変更しません(新しいインスタンスを返します)。また、パフォーマンスのためにいくつかの余分なものが含まれています。

9
eglasius

値型、値渡し、参照型、および参照渡しの違いを考える良い方法は次のとおりです。

変数はコンテナです。

値型変数にはインスタンスが含まれます。参照型変数には、他の場所に保存されているインスタンスへのポインタが含まれています。

値型変数を変更すると、それに含まれるインスタンスが変更されます。参照型変数を変更すると、それが指すインスタンスが変更されます。

個別の参照型変数は、同じインスタンスを指すことができます。したがって、同じインスタンスは、それを指す変数を介して変更できます。

値渡し引数は、コンテンツの新しいコピーを持つ新しいコンテナです。参照渡し引数は、元のコンテンツを持つ元のコンテナです。

値型の引数が値で渡される場合:コンテナは一意であるため、引数のコンテンツを再割り当てしても範囲外では効果がありません。インスタンスは独立したコピーであるため、引数を変更しても範囲外では効果がありません。

参照型の引数が値で渡される場合:コンテナーは一意であるため、引数のコンテンツを再割り当てしても範囲外には影響しません。引数のコンテンツを変更すると、コピーされたポインターが共有インスタンスを指すため、外部スコープに影響します。

引数が参照渡しされる場合:引数のコンテンツを再割り当てすると、コンテナが共有されるため、外部スコープに影響します。引数のコンテンツを変更すると、コンテンツが共有されるため、外部スコープに影響します。

結論として:

文字列変数は、参照型の変数です。したがって、他の場所に格納されているインスタンスへのポインタが含まれています。値で渡されると、そのポインターがコピーされるため、文字列引数を変更すると共有インスタンスに影響するはずです。ただし、文字列インスタンスには変更可能なプロパティがないため、文字列引数は変更できません。参照渡しの場合、ポインターのコンテナーは共有されるため、再割り当ては外部スコープに影響します。

6
Bryan

写真は千の言葉に値する」.

ここに簡単な例を示しますが、それはあなたの場合と似ています。

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

これは何が起こったかです:

enter image description here

  • 1行目と2行目:s1およびs2変数は、同じ"abc"文字列オブジェクトを参照しています。
  • 3行目: 文字列は不変 であるため、"abc"文字列オブジェクトは("def"に)それ自体を変更せず、代わりに新しい"def"文字列オブジェクトが作成されます、そしてs1参照します。
  • 4行目:s2は引き続き"abc"文字列オブジェクトを参照しているため、これが出力です。
4
Messi

上記の回答は参考になりますが、refキーワードなしでパラメータを渡すと、そのパラメータが参照型であっても何が起こるかを明確に示していると思う例を追加したいと思います:

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }
4
BornToCode

好奇心と会話を完了するために:はい、文字列は参照型です

unsafe
{
     string a = "Test";
     string b = a;
     fixed (char* p = a)
     {
          p[0] = 'B';
     }
     Console.WriteLine(a); // output: "Best"
     Console.WriteLine(b); // output: "Best"
}

ただし、この変更はnsafeブロックでのみ機能することに注意してください!なぜなら文字列は不変(MSDNから):

文字列オブジェクトの内容は、オブジェクトの作成後に変更することはできませんが、構文により、これを行うことができるように見えます。たとえば、このコードを記述すると、コンパイラは実際に新しい文字列オブジェクトを作成して新しい文字列を保持し、その新しいオブジェクトがbに割り当てられます。文字列「h」は、ガベージコレクションの対象となります。

string b = "h";  
b += "Ello";  

そして、次のことに留意してください。

文字列は参照型ですが、等価演算子(==および!=)は、参照ではなく文字列オブジェクトの値を比較するために定義されています。

2
Naser Yousefi

あなたのコードは次のコードに似ていると思いますが、ここにない同じ理由で値が変更されることを期待すべきではありません。

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }
0
Dave Cousineau