文字列は.NETでどのように渡されますか?


回答:


277

参照が渡されます。ただし、技術的に参照渡しされていませんこれは微妙ですが、非常に重要な違いです。次のコードを検討してください。

void DoSomething(string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomething(strMain);
    Console.WriteLine(strMain); // What gets printed?
}

ここで何が起きているかを理解するために知っておくべきことが3つあります。

  1. 文字列はC#の参照型です。
  2. これらも不変であるため、文字列を変更しているように見える操作を行っても、変更されません。完全に新しい文字列が作成され、参照がその文字列にポイントされ、古い文字列が破棄されます。
  3. 文字列は参照型strMainですが、参照渡しされません。これは参照型ですが、参照自体はvalueによって渡されますrefキーワードなしでパラメーターを渡す(パラメーターをカウントしないout)ときは常に、値で何かを渡しました。

つまり、参照を値渡ししているということです。参照型なので、参照のみがスタックにコピーされました。しかし、それはどういう意味ですか?

参照型を値で渡す:あなたはすでにそれをやっています

C#変数は、参照型または値型です。C#パラメータは、参照渡しまたは値渡しのいずれかです。ここでは用語が問題です。これらは同じように聞こえますが、違います。

ANYタイプのパラメーターを渡し、refキーワードを使用しない場合は、値で渡しています。値で渡した場合、実際に渡したのはコピーです。ただし、パラメーターが参照型の場合、コピーしたのは参照であり、それが指しているものではありません。

Mainメソッドの最初の行は次のとおりです。

string strMain = "main";

この行で2つのものを作成mainしました。メモリのどこかに値が格納された文字列と、strMainそれを指すと呼ばれる参照変数です。

DoSomething(strMain);

次に、その参照をに渡しDoSomethingます。値で渡したため、コピーを作成しました。これは参照型であるため、文字列自体ではなく、参照をコピーしました。これで、メモリ内の同じ値をそれぞれ指す2つの参照があります。

呼び出し先の内部

これがDoSomethingメソッドの一番上です:

void DoSomething(string strLocal)

いいえrefキーワード、そうしないstrLocalstrMain同じ値に指す2つの異なる参照です。再割り当てするとstrLocal...

strLocal = "local";   

...保存されている値は変更していません。呼び出された参照を受け取り、strLocalそれを新しいストリングに向けました。何が起こるまでstrMain、我々はそれを行うとき?何もない。それはまだ古い文字列を指しています。

string strMain = "main";    // Store a string, create a reference to it
DoSomething(strMain);       // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main" 

不変性

シナリオを少し変えましょう。文字列ではなく、作成したクラスなどの変更可能な参照型を処理しているとします。

class MutableThing
{
    public int ChangeMe { get; set; }
}

あなたがいる場合に従う参照objLocalそれが指すオブジェクトには、そのプロパティを変更することができます。

void DoSomething(MutableThing objLocal)
{
     objLocal.ChangeMe = 0;
} 

まだMutableThingメモリには1つしかなく、コピーされた参照と元の参照の両方がまだそれを指しています。自身のプロパティMutableThingが変更されました

void Main()
{
    var objMain = new MutableThing();
    objMain.ChangeMe = 5; 
    Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain

    DoSomething(objMain);                // now it's 0 on objLocal
    Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain   
}

ああ、でも文字列は不変です!ChangeMe設定するプロパティはありません。strLocal[3] = 'H'Cスタイルのchar配列の場合のようにC#で行うことはできません。代わりに、まったく新しい文字列を作成する必要があります。変更する唯一の方法strLocalは、参照を別の文字列にポイントするstrLocalことstrMainです。これは、ユーザーが何も影響を与えないことを意味します。値は不変であり、参照はコピーです。

参照による参照の受け渡し

違いがあることを証明するために、参照によって参照を渡すと次のようになります。

void DoSomethingByReference(ref string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomethingByReference(ref strMain);
    Console.WriteLine(strMain);          // Prints "local"
}

今回Mainは、スタックにコピーせずに参照を渡したため、文字列inは実際に変更されます。

そのため、文字列は参照型ですが、値で渡すと、呼び出し先で行われる処理が呼び出し元の文字列に影響を与えることはありません。ただし、これら参照型であるため、渡したい場合に、文字列全体をメモリにコピーする必要はありません。

その他のリソース:


3
@TheLight-申し訳ありませんが、「参照型はデフォルトで参照によって渡されます」と言うと、ここでは誤りです。デフォルトでは、すべてのパラメーターは値で渡されますが、参照タイプの場合、これは参照が値で渡されることを意味します。参照タイプを参照パラメーターと混同していますが、これは非常に混乱する区別であるため理解できます。ここで、値による参照型受け渡しセクションを参照してください。あなたのリンクされた記事はまったく正しいですが、それは実際に私の主張をサポートしています。
ジャスティンモーガン

1
@JustinMorganデッドコメントスレッドを表示するわけではありませんが、Cで考えると、TheLightのコメントは理にかなっていると思います。Cでは、データは単なるメモリブロックです。参照は、そのメモリブロックへのポインタです。メモリブロック全体を関数に渡すと、「値渡し」と呼ばれます。ポインタを渡すと、「参照渡し」と呼ばれます。C#では、メモリのブロック全体を渡すという概念がないため、「値渡し」を再定義して、ポインタを渡すことを意味します。これは間違っているようですが、ポインタもメモリのブロックにすぎません!私にとって、用語はかなり恣意的です
rliu

@roliu-問題は、Cで作業していないことです。C#は、その類似した名前と構文にもかかわらず、非常に異なっています。1つには、参照はポインタと同じではなく、そのように考えると落とし穴につながる可能性があります。ただし、最大の問題は、「参照渡し」がC#で非常に特定の意味を持ち、refキーワードが必要であることです。参照渡しが違いをもたらすことを証明するには、次のデモを参照してください。rextester.com
Justin Morgan

1
@JustinMorgan CとC#の用語を混在させることは悪いことだと思いますが、lippertの投稿を楽しんでいる間、参照をポインタと考えることは特にここで何かを覆い隠すことに同意しません。ブログの投稿では、参照をポインタとして考えることがいかに強力すぎるかについて説明しています。私がいることを承知しているrefキーワードは、ユーティリティを持って、私はちょうど1の理由を説明しようとしていたかもしれません C#で値によって参照型を渡すのだと思うと、参照渡し(と参照型を渡すの「伝統的な」(すなわちC)概念のように思えますC#での参照による参照は、参照を値によって参照に渡すようなものです。
rliu 2013

2
あなたは正しいですが、私は@roliuのような機能をどのように参照されたと思うFoo(string bar)と考えることができFoo(char* bar)、一方、がFoo(ref string bar)なりFoo(char** bar)(またはFoo(char*& bar)またはFoo(string& bar)C ++で)。確かに、それはあなたが毎日それをどう考えるべきかではありませんが、それは実際に私が最終的に内部で何が起こっているかを理解するのに役立ちました。
Cole Johnson

23

C#の文字列は不変の参照オブジェクトです。つまり、それらへの参照は(値によって)渡され、文字列が作成されると変更できなくなります。文字列の変更されたバージョン(サブストリング、トリミングされたバージョンなど)を生成するメソッドは、元の文字列の変更されたコピーを作成します。


10

文字列は特別な場合です。各インスタンスは不変です。文字列の値を変更すると、メモリ内に新しい文字列が割り当てられます。

つまり、参照のみが関数に渡されますが、文字列が編集されると、新しいインスタンスになり、古いインスタンスは変更されません。


4
文字列は、この側面では特別なケースではありません。同じセマンティクスを持つ不変オブジェクトを作成するのは非常に簡単です。(つまり、変更するメソッドを公開しない型のインスタンス...)

文字列は特殊なケースです-それらは値の型のように動作するという点で変更可能であるように見える事実上不変の参照型です。
謎解き2012年

1
@Enigmativityそのロジックでは、Uri(クラス)とGuid(構造体)も特殊なケースです。System.String他の不変の型...クラスまたは構造体の起源のどれよりも「値型」のようにどのように機能するかはわかりません。

3
@pst- 文字列には特別な作成セマンティクスがあります- Uri&とは異なりGuid、文字列リテラル値を文字列変数に割り当てるだけです。文字列intは再割り当てされているように変更可能であるように見えますが、暗黙的にオブジェクトを作成しています- newキーワードはありません。
謎解き2012年

3
文字列は特殊なケースですが、この質問には関係ありません。値の型、参照の型、この質問ではすべての型が同じように機能します。
カークブロードハースト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.