プラス記号を使用すると、いくつの文字列オブジェクトが作成されますか?


115

以下のコードでプラス記号を使用すると、いくつの文字列オブジェクトが作成されますか?

String result = "1" + "2" + "3" + "4";

以下のような場合、「1」、「2」、「12」の3つのStringオブジェクトを指定します。

String result = "1" + "2";

Stringオブジェクトは、パフォーマンス向上のためにString Intern Pool / Tableにキャッシュされることも知っていますが、それは問題ではありません。


文字列は、明示的にString.Internを呼び出した場合にのみインターンされます。
ジョーホワイト

7
@JoeWhite:彼らはいますか?
Igor Korkhov

13
結構です。すべての文字列リテラルは自動的にインターンされます。文字列演算の結果は違います。
Stefan Paul Noack

さらに、OPの例では、文字列定数は1つしかなく、インターンされます。説明のために回答を更新します。
Chris Shain

+1。そのスタイルで文字列連結をコード化する必要がある実際の例については、msdn.microsoft.com / en-us / library / の例セクションに、コンパイラーが最適化できなかった場合に不可能な例があります。属性パラメータに割り当てられた値の制約のため、単一の定数に。
ClickRick

回答:


161

驚いたことに、それは状況によります。

メソッドでこれを行う場合:

void Foo() {
    String one = "1";
    String two = "2";
    String result = one + two + "34";
    Console.Out.WriteLine(result);
}

その後、コンパイラはString.Concat@Joachimが回答したとおりにコードを発行するようです(彼に+1)。

それらを定数として定義すると、例えば:

const String one = "1";
const String two = "2";
const String result = one + two + "34";

または元の質問のように、リテラルとして:

String result = "1" + "2" + "3" + "4";

その後、コンパイラーはそれらの+兆候を最適化します。これは次と同等です。

const String result = "1234";

さらに、コンパイラーは無関係な定数式を削除し、使用または公開された場合にのみそれらを出力します。たとえば、このプログラム:

const String one = "1";
const String two = "1";
const String result = one + two + "34";

public static void main(string[] args) {
    Console.Out.WriteLine(result);
}

定数result(「1234」に等しい)を1つだけ生成します。 oneそしてtwo結果としてILには表示されません。

実行時にさらに最適化される可能性があることに注意してください。私は、ILが生成されたものをそのまま使用します。

最後に、インターンに関しては、定数とリテラルがインターンされますが、インターンされる値は、リテラルではなく、ILで結果として得られる定数値です。これは、複数の同一に定義された定数またはリテラルが実際には同じオブジェクトになるため、予想よりも少ない文字列オブジェクトを取得する可能性があることを意味します。これを以下に示します。

public class Program
{
    private const String one = "1";
    private const String two = "2";
    private const String RESULT = one + two + "34";

    static String MakeIt()
    {
        return "1" + "2" + "3" + "4";
    }   

    static void Main(string[] args)
    {
        string result = "1" + "2" + "34";

        // Prints "True"
        Console.Out.WriteLine(Object.ReferenceEquals(result, MakeIt()));

        // Prints "True" also
        Console.Out.WriteLine(Object.ReferenceEquals(result, RESULT));
        Console.ReadKey();
    }
}

文字列がループで(または動的に)連結される場合、連結ごとに1つの余分な文字列が作成されます。たとえば、次は12個の文字列インスタンスを作成します。2つの定数+ 10回の反復で、それぞれ新しいStringインスタンスになります。

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a";
        Console.ReadKey();
    }
}

しかし(驚くべきことに)、複数の連続した連結がコンパイラーによって1つのマルチストリング連結に結合されます。たとえば、このプログラムは12個の文字列インスタンスも生成します!これは、「1つのステートメントで複数の+演算子を使用しても、文字列の内容は1回だけコピーされるためです。

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}

文字列の結果はどうですか= "1" + "2" + 3 + 4; ここで、2と3はstring three = "3"のように宣言されています。文字列4 = "4" ;?
ライト

それでも1つの文字列になります。LinqPadを実行して、自分自身を再確認しました。
Chris Shain

1
@Servy-コメントが更新されたようです。コメントを変更すると、変更中としてマークされません。
セキュリティハウンド

1
完全性を検討するのに適した1つのケースは、ループでの連結です。例えば、次のコードは、どのように多くの文字列オブジェクトを割り当てるん:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren

1
LINQPad(linqpad.net)またはReflector(reflector.net)を使用しています。前者は任意のコードスニペットのILを示し、後者はアセンブリをILに逆コンパイルし、そのILから同等のC#を再生成できます。ILDASMと呼ばれる組み込みツールもあります(msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx)ILを理解するのは難しい作業です-codebetter.com/raymondlewallen/2005/を
02/07

85

クリス・シャインの答えはとても良いです。文字列連結オプティマイザを作成した人として、2つの興味深い点を追加します。

1つ目は、連結オプティマイザが安全に実行できる場合、本質的に括弧と左結合性の両方を無視することです。文字列を返すメソッドM()があるとします。あなたが言うなら:

string s = M() + "A" + "B";

その後、コンパイラーは加算演算子が結合型のままであると判断します。したがって、これは次と同じです。

string s = ((M() + "A") + "B");

しかしこれは:

string s = "C" + "D" + M();

と同じです

string s = (("C" + "D") + M());

これは、定数文字列 "CD"との連結ですM()

実際、連結オプティマイザは文字列連結が連想的であることを認識しString.Concat(M(), "AB")、最初の例を生成しますが、これは左結合性に違反しています。

あなたもこれを行うことができます:

string s = (M() + "E") + ("F" + M()));

まだ生成しString.Concat(M(), "EF", M())ます。

2番目の興味深い点は、nullと空の文字列が最適化されることです。したがって、これを行う場合:

string s = (M() + "") + (null + M());

あなたが得るでしょう String.Concat(M(), M())

次に興味深い質問が出されます:これはどうですか?

string s = M() + null;

それを最適化することはできません

string s = M();

理由M()はnullを返すかもしれませんが、String.Concat(M(), null)場合は、空の文字列を返すM()リターンがヌル。だから私たちがすることは代わりに減らすことです

string s = M() + null;

string s = M() ?? "";

これにより、文字列の連結が実際に呼び出す必要がないことを示しString.Concatます。

この主題の詳細については、以下を参照してください。

String.ConcatがStringBuilder.Appendに最適化されないのはなぜですか?


そこにはいくつかのエラーが含まれていると思います。確かに、ではなく("C" + "D") + M())生成します。さらに下では、ではなくを生成する必要があります。String.Concat("CD", M())String.Concat(M(), "AB")(M() + "E") + (null + M())String.Concat(M(), "E", M())String.Concat(M(), M())
ハマー

21
開始段落の+1。:)このような答えは、スタックオーバーフローについていつも私を驚かせます。
brichins

23

その答えはMSDNで見つかりました。1。

方法:複数の文字列を連結する(C#プログラミングガイド)

連結は、ある文字列を別の文字列の末尾に追加するプロセスです。+演算子を使用して文字列リテラルまたは文字列定数を連結すると、コンパイラは単一の文字列を作成します。実行時の連結は発生しません。ただし、文字列変数は実行時にのみ連結できます。この場合、さまざまなアプローチのパフォーマンスへの影響を理解する必要があります。


22

一つだけです。C#コンパイラは文字列定数を折りたたむため、基本的に次のようにコンパイルされます。

String result = "1234";

""を使用するとStringオブジェクトが作成されると思いました。
ライト

1
@ウィリアム一般的にはい。ただし、一定のフォールディングにより、不要な中間ステップが削除されます
JaredPar

13

これが標準や仕様によって義務付けられているとは思えません。1つのバージョンが別のバージョンと異なる可能性があります。


3
これは、少なくともVS 2008および2010用のMicrosoftのC#コンパイラの動作を文書化したものです(@ David-Strattonの回答を参照)。とは言え、あなたは正しいです-私が簡単な閲覧からわかる限り、C#仕様はこれを指定しておらず、おそらく実装の詳細と見なされるべきです。
クリスシャイン

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.