2009-07-08 15 views
137

私はC#の "string"が参照型であることを知っています。これはMSDNにあります。しかし、このコードは、それがその後、必要として機能しません。C#文字列参照型ですか?

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メソッドで変更されたテキストを認識する必要があります。しかし、「渡す前に」「渡す前に」取得し、refではなく値渡しと思われるようにします。私は文字列が不変であることを理解していますが、ここで何が起こっているのかがわかりません。私は何が欠けていますか?ありがとう。

+0

下記のJonが参照する記事を参照してください。あなたが言及する振る舞いは、C++ポインタによっても再現できます。 – Sesh

+0

[MSDN](http://msdn.microsoft.com/en-us/library/s6938f28.aspx)にも非常に良い説明があります。 –

+0

[C#では、Stringは値型のように動作する参照型はなぜですか?](http://stackoverflow.com/questions/636932/in-c-why-is-string-a-reference-type -refresh-like-a-value-type) – Liam

答えて

185

文字列への参照は値渡しです。値による参照の受け渡しと参照によるオブジェクトの受け渡しには大きな違いがあります。どちらの場合でも「参照」という言葉が使用されていることは残念です。あなたは参照によって文字列参照を通過しない場合は、期待どおり、それは動作します

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"); 
    } 
} 

は詳細についてはmy article on parameter passingを参照してください。

+2

が同意しただけで、ref修飾子を使用すると参照型以外の型でも動作することを明確にしたいと思います。 – eglasius

+2

@Jon Skeetはあなたの記事の中のサイドノートを大好きでした。あなたはあなたの答えとして '参照 'してください。 –

0

試してみてください。


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

答えには単なるコード以上のものが含まれているはずです。また、それがなぜ機能するかについての説明も含まれていなければならない。 –

9

は、実際にはそのことについては任意のオブジェクトは、すなわち参照型であることと、参照渡しのために同じであったであろうが、C#で2つの異なるものです。

これは動作しますが、それは種類に関係なく適用されます。

public static void TestI(ref string test) 

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

25

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

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

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

test変数の値を渡すことはないが、参照のみを渡すので、refを関数宣言および呼び出しの作業に入れるための推奨される解決策。したがって、関数内の変更は元の変数を反映します。

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

0

私はあなたのコードは次のように似ていると信じて、そしてあなたは値が、それはここにいないのと同じ理由で変更されていると期待してはならない。

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 
} 
6

ここで考えるための良い方法です値渡し、参照渡し、および参照渡しの違い:

変数はコンテナです。

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

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

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

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

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

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

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

結論:

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

18

他にも述べたように、.NETのStringタイプは不変であり、参照は値渡しです。すぐにこの行が実行されるように、元のコードでは、

test = "after passing"; 

次いでtestは、元のオブジェクトにを参照もはやではありません。 新しいStringオブジェクトを作成し、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"; 
    } 
} 
2

有用である、私はちょうど私たちは、そのパラメータが参照型である場合でも、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. 
     } 
関連する問題