web-dev-qa-db-ja.com

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

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

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

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

String result = "1" + "2";

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

115
The Light

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

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

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

次に、コンパイラはString.Concatを使用してコードを発行しているようです。

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

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

またはliteralsとして、元の質問のように:

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();
    }
}
161
Chris Shain

クリス・シャインの答えはとても良いです。文字列連結オプティマイザを作成した人として、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()を連結したものです。

実際、連結オプティマイザは文字列連結がassociativeであることを認識し、最初の例では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()がnullを返す場合、空の文字列を返します。だから私たちがすることは代わりに減らすことです

_string s = M() + null;
_

_string s = M() ?? "";
_

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

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

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

85
Eric Lippert

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

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

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

23
David

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

String result = "1234";
22
JaredPar

1つは、静的であるため、コンパイラーはコンパイル時にそれを単一の文字列に最適化できることです。

それらが動的であった場合、それらは String.Concat(string、string、string、string) への1回の呼び出しに最適化されています。

13

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

13