String.Formatと文字列の連結を使用するほうがよいのはいつですか?


120

Excelへのセル入力を決定するためにインデックス値を解析する小さなコードがあります。考えさせられて...

違いは何ですか

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

そして

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

どちらか一方が「より良い」ですか?なぜ?



回答:


115

C#6より前

正直に言うと、最初のバージョンの方が単純だと思います。ただし、次のように単純化します。

xlsSheet.Write("C" + rowIndex, null, title);

私は他の回答パフォーマンスの打撃について話しているのではないかと思いますが、正直に言うとそれがあったとしても最小限です -そして、この連結バージョンはフォーマット文字列を解析する必要はありません。

ローカリゼーションなどの目的でフォーマット文字列は優れていますが、このような場合は連結が単純で、同じように機能します。

C#6を使用

文字列補間により、C#6で多くのことを簡単に読み取ることができます。この場合、2番目のコードは次のようになります。

xlsSheet.Write($"C{rowIndex}", null, title);

これはおそらく最良の選択肢ですIMO。


6
@nawfal:msmvps.com/blogs/jon_skeet/archive/2008/10/08/…を参照してください
Jon Skeet

分かった分かった。それは冗談で作られました(以前にリンクを読んだことがあり、これは良い読み物でした)
nawfal

ジョン。私はずっとリヒターさんのファンで、ボクシングなどの指導を忠実にこなしてきました。しかし、あなたの(古い)記事を読んだ後、私は今改宗しています。ありがとう
stevethethread 2014年


4
今C#6が利用可能であることを、あなたは私も読みやすくはあると思います何のために新しい文字列補間構文を使用することができますxlsSheet.Write($"C{rowIndex}", null, title);
HotN

158

私の最初の好み(C ++の背景からのもの)はString.Formatでした。次の理由により、これを後で落としました。

  • 文字列の連結は間違いなく「安全」です。パラメータを削除したり、誤ってパラメータの順序をめちゃくちゃにしたりしました(他のいくつかの開発者にも起こりました)。コンパイラーはフォーマット文字列に対してパラメーターをチェックせず、実行時エラーが発生します(つまり、エラーをログに記録するなど、あいまいなメソッドでそれを実行できないのが幸運な場合)。連結を使用すると、パラメーターを削除してもエラーが発生しにくくなります。あなたは非常に小さいですが、それは誤りの可能性を主張する可能性があり起こります。

-文字列連結ではnull値が許可されますが、許可されString.Formatません。「s1 + null + s2」を書き込んでも問題はなく、null値がString.Emptyとして扱われるだけです。まあ、これはあなたの特定のシナリオに依存するかもしれません-nullのFirstNameを黙って無視する代わりにエラーが欲しい場合があります。ただし、この状況でも、私は自分でnullをチェックし、String.Formatから取得する標準のArgumentNullExceptionではなく、特定のエラーをスローすることを好みます。

  • 文字列連結のパフォーマンスが向上します。上記の投稿のいくつかはすでにこれについて言及しています(実際に理由を説明せずに、私がこの投稿を書くことにしました:)。

考えられるのは、.NETコンパイラーがこのコードを変換するのに十分スマートであるということです。

public static string Test(string s1, int i2, int i3, int i4, 
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

これに:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4, 
                    " ddd ", s5, s6, f7, f8 });
}

String.Concatの内部で何が起こるかは簡単に推測できます(Reflectorを使用)。配列内のオブジェクトは、ToString()を介して文字列に変換されます。次に、全長が計算され、1つの文字列のみが(全長で)割り当てられます。最後に、各文字列は、安全でないコードの一部でwstrcpyを介して結果の文字列にコピーされます。

理由String.Concatはずっと速いですか?まあ、私たちはすべて何String.Formatが行われているのかを見ることができます-あなたはフォーマット文字列を処理するために必要なコードの量に驚かれることでしょう。これに加えて(メモリ消費に関するコメントを見てきました)、String.Format内部でStringBuilderを使用します。方法は次のとおりです。

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

したがって、渡された引数ごとに8文字を予約します。引数が1桁の値である場合は、悪くなりすぎて、無駄なスペースができてしまいます。引数がに長いテキストを返すカスタムオブジェクトの場合、ToString()再割り当てが必要になることもあります(もちろん、最悪のシナリオ)。

これと比較して、連結はオブジェクト配列のスペースを浪費するだけです(参照の配列であることを考慮すると、多すぎません)。形式指定子の解析や中間のStringBuilderはありません。ボックス化/ボックス化解除のオーバーヘッドは、どちらの方法にも存在します。

String.Formatを使用する唯一の理由は、ローカリゼーションが関係している場合です。リソースにフォーマット文字列を配置すると、コードをいじらずにさまざまな言語をサポートできます(フォーマットされた値が言語によって順序が変わるシナリオについて考えてください。つまり、「{0}時間後と{1}分後」は、日本語ではかなり異なって見える場合があります: )。


私の最初の(そしてかなり長い)投稿を要約すると:

  • 私にとって最良の方法(パフォーマンスと保守性/可読性の観点から)は、ToString()呼び出しなしで文字列連結を使用することです
  • パフォーマンスの後である場合は、ToString()ボクシングを回避するように自分で呼び出します(私は読みやすさに偏っています)-質問の最初のオプションと同じ
  • ローカライズされた文字列をユーザーに表示している場合(ここでは該当しません)、String.Format()エッジがあります。

5
1)string.FormatReSharperを使用する場合、「安全」です。つまり、[誤って]使用できる他のコードと同じくらい安全です。2)string.Format ない「安全」を可能にnullstring.Format("A{0}B", (string)null)「AB」の結果。3)このレベルのパフォーマンスを気にすることはめったにありません(そして、そのために、私が引っ張るのはまれな日StringBuilderです)...

2)に同意し、投稿を編集します。これが1.1で安全だったかどうかを確認することはできませんが、最新のフレームワークは実際にnullセーフです。
Dan C.

オペランドの1つがパラメーターや変数ではなく、戻り値を持つメソッド呼び出しである場合、string.Concatは引き続き使用されますか?
Richard Collette 2013年

2
@RichardColletteはい、String.Concatは、メソッド呼び出しの戻り値を連結する場合でも使用されます。たとえば、リリースモードの呼び出しにstring s = "This " + MyMethod(arg) + " is a test";コンパイルされString.Concat()ます。
Dan C.

素晴らしい答え。非常によく書かれ、説明されています。
フランクV

6

私は最初のオプションがより読みやすく、それがあなたの主な関心事であると思います。

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Formatは内部でStringBuilderを使用するので(反射器で確認)、大量の連結を行わない限り、パフォーマンス上の利点はありません。シナリオの速度は遅くなりますが、実際には、このマイクロパフォーマンス最適化の決定はほとんどの場合不適切であり、ループに入っていない限り、コードの読みやすさに焦点を当てるべきです。

どちらの方法でも、まず読みやすさを優先して記述し、パフォーマンスに懸念があると本当に思われる場合は、パフォーマンスプロファイラーを使用してホットスポットを特定します。



5

それは単純な単一連結のシンプルなケースでは、私はそれは複雑価値はないと感じてstring.Format(と私がテストしていませんが、私はこのような単純な場合のために、と思われるstring.Format かもしれません少し遅くなることが、どのような形式の文字列解析ととすべて)。Jon Skeetと同様に.ToString()string.Concat(string, object)オーバーロードによって暗黙的に行われるため、私は明示的にを呼び出さないようにしています。コードは見た目がきれいで、コードがなくても読みやすいと思います。

しかし、いくつかの連結(いくつが主観的であるか)を超える連結については、私は間違いなく優先しstring.Formatます。ある時点で、連結によって読みやすさとパフォーマンスの両方が不必要に低下すると思います。

フォーマット文字列に多くのパラメーターがある場合(ここでも、「多く」は主観的です)、どのパラメーターがどの値に移動するかを追跡できないように、通常は置換引数にコメント化されたインデックスを含めることを好みます。不自然な例:

Console.WriteLine(
    "Dear {0} {1},\n\n" +

    "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary",

    /*0*/client.Title,
    /*1*/client.LastName,
    /*2*/client.Pet.Animal,
    /*3*/client.Pet.Name,
    /*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
    /*5*/client.Pet.Schedule[0]
);

更新

連結とここの両方を使用したように見えるので、私が提供した例は少し混乱しているように思いstring.Formatます。そして、はい、論理的かつ語彙的に、それは私がやったことです。ただし、連結はすべて文字列リテラルであるため、コンパイラ1によってすべて最適化されます。したがって、実行時には単一の文字列が存在します。ですから、実行時には多くの連結を避けたいと言うべきでしょう。

もちろん、このトピックのほとんどは、C#5以前を使用し続けている場合を除き、現在は古くなっています。これで、ほとんどすべての場合において、読みやすくするためにに比べてはるかに優れた補間文字列ができましたstring.Format。最近では、文字列リテラルの最初または最後に直接値を連結しているのでない限り、ほとんど常に文字列補間を使用しています。今日は、以前の例を次のように記述します。

Console.WriteLine(
    $"Dear {client.Title} {client.LastName},\n\n" +

    $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
    $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
    $"{client.Pet.Schedule[0]} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary"
);

この方法では、コンパイル時の連結が失われます。 補間された各文字列はstring.Format、コンパイラによってへの呼び出しに変換され、その結果は実行時に連結されます。これは、読みやすさのために実行時のパフォーマンスが犠牲になることを意味します。ほとんどの場合、実行時のペナルティは無視できるので、それは価値のある犠牲です。ただし、パフォーマンスが重要なコードでは、さまざまなソリューションをプロファイルする必要がある場合があります。


1 これはC#仕様で確認できます。

...次の構文は定数式で使用できます:

...

  • 事前定義された+ ...二項演算子...

小さなコードで確認することもできます:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";

1
すべての連結の代わりに、@ "... multi line string"を使用できます。
Aaron Palmer、

はい。ただし、文字列を左揃えにする必要があります。@文字列には、引用符の間にあるすべての改行とタブ文字が含まれます。
Pダディ

私はこれが古いことを知っていますが、これはフォーマット文字列をresxファイルに入れるケースです。
アンディ

2
うわー、誰もが問題の核心ではなく文字列リテラルに焦点を合わせています。
Pダディ

heheh -私はちょうどあなたの文字列連結の内部に気付いたString.Format()
のKristopher

3

文字列が多くの変数が連結されてより複雑な場合は、string.Format()を選択します。しかし、あなたのケースで連結されている文字列のサイズと変数の数については、私はあなたの最初のバージョンで行くでしょう、それはより質素です。


3

(Reflectorを使用して)String.Formatを確認しましたが、実際にはStringBuilderが作成され、その上でAppendFormatが呼び出されます。したがって、複数の撹拌の場合は、連結よりも高速です。最も速い(私は信じています)は、StringBuilderを作成し、Appendへの呼び出しを手動で行うことです。もちろん、「多数」の数は推測の対象です。私はあなたの例のように単純なもののために+(実際には&私はほとんどVBプログラマーであるため)を使用します。より複雑になると、String.Formatを使用します。変数がたくさんある場合は、StringBuilderとAppendを使用します。たとえば、コードをビルドするコードがある場合、1行の実際のコードを使用して1行の生成されたコードを出力します。

これらの各操作で作成される文字列の数については、いくつかの推測があるようです。簡単な例をいくつか挙げてみましょう。

"C" + rowIndex.ToString();

「C」はすでに文字列です。
rowIndex.ToString()は別の文字列を作成します。(@manohard-rowIndexのボックス化は発生しません)
次に、最終的な文字列を取得します。
例をとると

String.Format("C(0)",rowIndex);

文字列
rowIndexがボックス化されて関数に渡されると、「C {0}」になります
。新しい
文字列ビルダーが作成されます。文字列ビルダーでAppendFormatが呼び出されます-AppendFormat関数の詳細はわかりませんが、非常に効率的ですが、ボックス化されたrowIndexを文字列に変換する必要があります。
次に、文字列ビルダーを新しい文字列に変換します。
StringBuilderは無意味なメモリコピーが行われないようにしようとしますが、String.Formatは通常の連結に比べて余分なオーバーヘッドが発生します。

今いくつかの文字列を使って例をとると

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

最初に6つの文字列があります。これはすべての場合で同じです。
連結を使用すると、4つの中間文字列と最終結果も得られます。String、Format(またはStringBuilder)を使用することで削除されるのは、これらの中間結果です。
各中間文字列を作成するには、前の文字列を新しいメモリの場所にコピーする必要があることに注意してください。メモリの割り当てが遅いだけではありません。


4
Nitpick。"a" + ... + "b" + ... + "c" + ...では、実際には4つの中間文字列はありません。コンパイラーはString.Concat(params string [] values)静的メソッドへの呼び出しを生成し、それらはすべて一度に連結されます。ただし、読みやすさのためにstring.Formatを優先します。
Pダディ

2

String.Formatが好きなのは、インライン連結よりも書式設定されたテキストの追跡と読み取りがはるかに簡単になるためです。また、より柔軟にパラメーターをフォーマットできるためです。ただし、このような短い使用の場合は、連結について問題はありません。

ループ内または大きな文字列内の連結では、常にStringBuilderクラスを使用する必要があります。


2

その例は、違いに気付くにはおそらく些細なことです。実際、ほとんどの場合、コンパイラーは違いをまったく最適化できないと思います。

ただし、推測しなければならない場合はstring.Format()、より複雑なシナリオの方が有利です。しかし、それは、実際のデータに基づいていない複数の不変文字列を生成する代わりに、バッファを利用してより良い仕事をする可能性が高いという、より直感的な感覚です。


1

上記の多くの点に同意します。言及すべきもう1つの点は、コードの保守性です。string.Formatを使用すると、コードを簡単に変更できます。

すなわち、私はメッセージを持っている "The user is not authorized for location " + location"The User is not authorized for location {0}"

メッセージを次のように変更したい場合: location + " does not allow this User Access"または "{0} does not allow this User Access"

string.Formatを使用して、文字列を変更するだけです。連結するには、そのメッセージを変更する必要があります

複数の場所で使用すると、時間の割り当てを節約できます。


1

string.formatの方が速いという印象を受けました。このテストでは3倍遅いようです

string concat = "";
        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch    ();
        sw1.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
        }
        sw1.Stop();
        Response.Write("format: "  + sw1.ElapsedMilliseconds.ToString());
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
        }
        sw2.Stop();

string.formatは4.6秒かかり、「+」を使用すると1.6秒かかりました。


7
コンパイラーは"1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"1つの文字列リテラルとして認識するため、前の行"12345678910" + iよりも速い行が効果的になりますstring.Format(...)
wertzui

0

string.Formatは、フォーマットテンプレート( "C {0}")が構成ファイル(Web.config / App.configなど)に格納されている場合、おそらくより良い選択です。


0

string.Format、StringBuilder、文字列連結など、さまざまな文字列メソッドのプロファイリングを少し行いました。文字列の連結は、ほとんどの場合、文字列を構築する他の方法よりも優れていました。したがって、パフォーマンスが重要な場合は、その方が優れています。ただし、パフォーマンスがそれほど重要でない場合は、個人的にstring.Formatをコードで理解しやすいと思います。(しかし、それは主観的な理由です)ただし、StringBuilderは、メモリ使用率に関しておそらく最も効率的です。



-1

文字列の連結は、String.Formatに比べて多くのメモリを必要とします。文字列を連結する最良の方法は、String.FormatまたはSystem.Text.StringBuilderオブジェクトを使用することです。

最初のケースを考えてみましょう: "C" + rowIndex.ToString()rowIndexが値型であると仮定して、ToString()メソッドがBoxで値を文字列に変換し、CLRが両方の値を含む新しい文字列のメモリを作成する必要があるとします。

string.Formatはオブジェクトパラメータを期待し、rowIndexをオブジェクトとして受け取り、それを内部的に文字列に変換しますが、ボクシングはありますが、それは本質的であり、最初のケースほどメモリを消費しません。

短い文字列の場合、私はそれほど重要ではないと思います...

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