文字列出力:C#でフォーマットまたは連結?


178

文字列を出力または連結したいとします。次のスタイルのうち、どちらが好きですか。

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

形式を使用するのですか、それとも単に文字列を連結するのですか?あなたのお気に入りは?これらの1つはあなたの目を傷つけていますか?

どちらか一方を使用し、もう一方を使用しないという合理的な議論はありますか?

私は二番目に行きます。

回答:


88

このコードを試してください。

これは、コードを少し変更したバージョンです。
1. Console.WriteLineを削除しました。これは、測定しようとしているものよりも数桁遅いためです。
2.ループの前にストップウォッチを開始し、直後に停止します。これにより、関数の実行にたとえば26.4ティックを使用しても、精度が失われることはありません。
3.結果をいくつかの反復で分割する方法が間違っていました。1000ミリ秒と100ミリ秒があるとどうなるかを確認してください。どちらの場合も、1000000で除算した後、0ミリ秒になります。

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

それらは私の結果です:

1000000 x結果= string.Format( "{0} {1}"、p.FirstName、p.LastName); かかりました:618ms-2213706ティック
1000000 x結果=(p.FirstName + "" + p.LastName); かかった時間:166ミリ秒-595610ティック


1
とても興味深い。48msに対して平均224msで、x4.66の改善で、x3.72よりも優れています。string.Format複合フォーマット機能を使用せず(つまり、単純な{0})、ILを書き直して、かなり高速な文字列連結に置き換えることができるコンパイル後のツールがあるのでしょうか。このような偉業は、PostSharpなどの既存のILリライターで実現できるのではないでしょうか。
Allon Guralnek、2011

31
文字列は不変です。つまり、同じ小さなメモリがコードで何度も使用されます。同じ2つの文字列を一緒に追加し、同じ新しい文字列を繰り返し作成しても、メモリには影響しません。.Netは、同じメモリ参照を使用するだけで十分スマートです。したがって、コードは2つのconcatメソッドの違いを実際にはテストしません。以下の私の答えのコードを参照してください。
Ludington、2013年

1
正直に言うと、読みやすく、
すごく

それで、速度が他のものを選択する唯一の理由ですか?
niico

158

非常に多くの人々がすぐに最も速く実行されるコードを見つけたいと思っていることに私は驚いています。100万回の反復処理に1秒もかからない場合、これはエンドユーザーに気付かれることはありますか?あまりない。

時期尚早の最適化=失敗。

String.Formatアーキテクチャの観点から最も理にかなっているという理由だけで、オプションを選択します。問題になるまで、パフォーマンスは気にしません(問題が発生する場合は、自分で質問します。一度に100万の名前を連結する必要はありますか?確かに、すべてが画面に収まらない...)

あなたの顧客は、後でので、彼らが表示するかどうかを設定することができ、それを変更したい場合を検討し"Firstname Lastname"たり"Lastname, Firstname."、フォーマット文字列アウトだけスワップ-フォーマットオプションを使用すると、これは簡単です。連結では、追加のコードが必要になります。確かに、この特定の例では大したことには聞こえませんが、外挿しています。


47
「時期尚早の最適化==失敗」という点で良い点は、はい。ただし、実行フットプリント(サービスとしてのクラウドとインフラストラクチャなど)の支払いを開始したり、何かで100万人のユーザーのサポートを開始したりすると、リクエストに対する単一ユーザーへの応答は問題になりません。ユーザーへのリクエストを処理するコストは、
最終的な収益の

23
これはまったく間違っています。Web開発環境では、多くの場合、文字列生成コードはモデル、ビュー、コントローラーの両方で深くなり、ページの読み込みごとに何万回も呼び出されることがあります。文字列生成コードの評価に費やす時間を50%削減することは、大きな勝利となる可能性があります。
Benjamin Sussman、2011

2
このような質問は、OPの1つのインスタンスに適用されるだけではありません。その答えは、「文字列をどのように組み立てるべきか」ということを人々が覚えているようなものです。コードをすべて書いているからです。
Phil Miller

6
@ベンジャミン:...その場合、あなたはプロファイリングし、それがあなたのボトルネックであることがわかります。でも、どこからでもそれを引き出しているだけだと私はお金を賭けるでしょう。過去に多数のWebアプリケーションを作成およびプロファイルしたことがあるので、ほとんどの場合、(サーバー側の)応答時間のボトルネックがデータベースクエリであることがわかりました。
BlueRaja-Danny Pflughoeft 2013

2
これは間違いなく時期尚早の最適化ではありません。かなりの誤り。文字列のパフォーマンスは、特に.NETで多くのフォーマットと文字列の構築を行っている場合、UIを完全に停止させる可能性があります。ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

親愛なる-他の返信の1つを読んだ後、操作の順序を逆にしてみました-したがって、最初に連結を実行し、次にString.Format ...

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

したがって、操作の順序によって大きな違いが生じます。つまり、最初の操作は常にずっと遅くなります。

これは、操作が複数回完了した実行の結果です。私は順序を変更しようとしましたが、最初の結果が無視されると、物事は一般的に同じルールに従います:

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

ご覧のとおり、同じメソッドの後続の実行(コードを3つのメソッドにリファクタリングしました)は徐々に高速になっています。最も速いのは、Console.WriteLine(String.Concat(...))メソッドで、通常の連結、フォーマットされた操作の順に表示されます。

起動時の最初の遅延は、最初の操作がすべての時間を再開する前にConsole.Writeline( "Start!")を配置するため、おそらくコンソールストリームの初期化です。


2
次に、テストからConsole.WriteLineを完全に削除します。結果を歪めている!
CShark、2016年

この正確な理由でパフォーマンステストを実行するとき、私はいつも使い捨てまたは「制御」シナリオから始めます
drzaus

36

文字列は不変です。つまり、同じ小さなメモリがコードで何度も使用されます。同じ2つの文字列を一緒に追加し、同じ新しい文字列を繰り返し作成しても、メモリには影響しません。.Netは、同じメモリ参照を使用するだけで十分スマートです。したがって、コードは2つのconcatメソッドの違いを実際にはテストしません。

サイズを試してみてください:

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

出力例:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks

1
StringBuilderとサンプル出力を回答に追加しました
mikeschuld

使用することstring.Formatは、ここでの小さなパフォーマンスヒットの価値があると思います。構造的には、フォーマットをより簡単に変更できるという意味で優れています。しかし、文字列ビルダーは私には本当に意味がありません。ここにある他のすべてのスレッドは、文字列を連結する代わりにStringbuilderを使用するべきだと言っています。利点は何ですか?このベンチマークが証明するように、明らかに速度ではありません。
roryok 2015

22

かわいそうな翻訳者たち

アプリケーションが英語のままであることがわかっている場合は、時計の刻みを保存してください。ただし、多くの文化では、通常、たとえばアドレスに姓名が表示されます。

そのためstring.Format()、特に、アプリケーションが英語を母国語としない場所に移動させる場合は、を使用してください。


2
string.Format()異なる文化ではどのように動作が異なりますか?それでも、姓、名の順に印刷しませんか?どちらの状況でも、異なる文化を考慮する必要があるようです。ここで何かが足りないような気がします。
Broots Waymb 2016

2
私はどのようにでしょう.. @DangerZoneに同意string.Format()あなたがアドレスの名前を使用していた知っていますか?場合string.Format()スワップ{0} {1}文化に基づいて、私はそれが壊れて検討します。
Alex McMillan

2
Jeremyが試みていた点は、さまざまな国をサポートするために説明されたシナリオでは、フォーマット文字列自体を言語リソースに抽出することが適切である可能性があるということです。ほとんどの場合、文字列は "{0} {1}"になりますが、姓優先が一般的な操作である国(ハンガリー、香港、カンボジア、中国、日本、韓国、マダガスカル、台湾、ベトナム、インドの一部)その文字列は "{1} {0}"になります。
リチャードJフォスター

確かに。または、より微妙に、個人の属性としてフォーマット文字列を追加します。たとえば、姓の後に姓を使いたいのですが、同僚のベンはそうではありません。
Jeremy McGee

14

これが100,000回を超える反復の結果です。

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

そしてここにベンチコードがあります:

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

だから、答えとしてマークする返信が誰なのかわかりません:)


この回答の背景が青色であるのはなぜですか?
user88637 2009年

@yossi回答者が質問者と同じであるため青色です
Davy8

9

文字列の連結は、そのような単純なシナリオでは問題ありません。LastName、FirstNameでさえ、それよりも複雑なものであればさらに複雑になります。この形式では、コードを読み取ったときに文字列の最終的な構造が一目でわかるため、連結すると、最終結果をすぐに見分けることがほぼ不可能になります(このような非常に単純な例を除いて)。

長い目で見れば、文字列の形式を変更するために戻ったときに、ポップインして形式文字列にいくつかの調整を加えるか、眉をしわにしてすべての動きを始めることができます。問題を引き起こす可能性が高い、テキストと混合された種類のプロパティアクセサー。

.NET 3.5を使用している場合は、次のような拡張メソッドを使用して、次のようなカフ構文から簡単に流れるようにすることができます。

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

最後に、アプリケーションの複雑さが増すにつれて、アプリケーション内の文字列を適切に維持するために、それらをリソースファイルに移動してローカライズするか、単に静的ヘルパーに移動することができます。これは、一貫してフォーマットを使用している場合にはるかに簡単に達成でき、コードをリファクタリングして次のようなものを使用できます

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);

7

非常に単純な操作には連結を使用しますが、2つまたは3つの要素を超えると、フォーマットがより適切なIMOになります。

String.Formatを選択するもう1つの理由は、.NET文字列は不変であり、この方法で作成すると、一時的/中間コピーが少なくなることです。


6

私はスタイルの好みを完全に理解し、自分の好みに部分的に基づいて最初の回答の連結を選択しましたが、私の決定の一部は、連結がより速くなるだろうという考えに基づいていました。それで、好奇心から、私はそれをテストしました、そして特にそのような小さなひもに関して、結果は驚異的でした。

次のコードを使用します。

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

次の結果が得られました。

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

フォーマット方法を使用すると、100倍以上遅くなります!! 連結は1msとしても登録されなかったため、タイマーティックも出力しました。


2
ただし、測定を取得するには、操作を複数回実行する必要があります。
erikkallen 2009年

2
そして、質問の範囲を超えているので、Console.Writeline()の呼び出しを失いますか?
Aidanapword 2010

文字列ビルダーでテストしましたか?;)
niico 2016年

6

C#6.0以降では、補間された文字列を使用してこれを行うことができます。これにより、フォーマットがさらに簡素化されます。

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

補間された文字列式は、式を含むテンプレート文字列のように見えます。補間された文字列式は、含まれている式を式の結果のToString表現で置き換えることによって文字列を作成します。

補間された文字列のパフォーマンスはString.Formatと似ていますが、値と式がインラインで挿入されるため、読みやすさが向上し、構文が短くなります。

文字列の補間に関するこのdotnetperlsの記事も参照してください。

文字列をフォーマットするデフォルトの方法を探している場合、これは読みやすさとパフォーマンスの点で理にかなっています(マイクロ秒が特定のユースケースで違いを生む場合を除く)。


5

基本的な文字列の連結には、一般的に2番目のスタイルを使用します。読みやすく、単純です。ただし、より複雑な文字列の組み合わせを実行している場合は、通常、String.Formatを選択します。

String.Formatは多くの引用符とプラス記号を節約します...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

数文字しか保存されませんでしたが、この例では、フォーマットを使用することでよりきれいになりました。


5

より良いテストは、PerfmonとCLRメモリカウンターを使用してメモリを監視することです。文字列は不変なので、次のパスで再利用する必要のある一時的な文字列でガベージコレクターに不必要な負荷をかける必要があるというのが私の理解です。

StringBuilderとString.Formatは、潜在的に低速ですが、メモリ効率が高くなります。

文字列連結の何がそんなに悪いのですか?


同意する; すべての文字列操作により、文字列の新しいコピーが作成されます。そのメモリはすべて、遅かれ早かれガベージコレクタによって回収されます。したがって、多くの文字列を割り当てると、後で戻ってきてしまうかもしれません。
Marnix van Valen、

5

特に文字列が長くなると読みやすくなるので、一般的には前者を好みます。

もう1つの利点は、後者が実際に2つの文字列作成ステートメントを実行してから最終的な文字列をConsole.Writeメソッドに渡すため、パフォーマンスの1つであると私は考えています。String.Formatは、私が信じているカバーの下でStringBuilderを使用しているため、複数の連結は避けられます。

ただし、String.Format(およびConsole.Writeなどの他のメソッド)に渡すパラメーターが値型の場合は、渡される前にボックス化されるため、独自のパフォーマンスヒットが得られる可能性があることに注意してください。これに関するブログ投稿はこちら


1
そのブログ投稿は現在jeffbarnes.net/blog/post/2006/08/08/…にあります。編集するには担当者が不十分です。
リチャードスレーター

5

2015年8月19日から1週間後、この質問はちょうど7歳になります。これを行うためのより良い方法があります。文字列を連結するだけに比べてパフォーマンステストを行っていないので、保守性の点で優れています(しかし、最近では問題になっていますか?数ミリ秒の違いはありますか?)。C#6.0でそれを行う新しい方法:

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

この新機能はIMOの優れており、実際には、いくつかの要因に依存する値を持つクエリ文字列を作成するコードがあるため、このケースではより優れています。6つの引数を持つ1つのクエリ文字列を想像してください。したがって、たとえば、代わりに:

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

inは次のように書くことができ、読みやすくなります。

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";

実際、C#6.0の新しい方法は、以前の選択肢よりも優れています-少なくとも読みやすさの観点から。
フィリップ

そのとおり。また、オブジェクトを目的の場所に直接配置するため、どのオブジェクトがどのインデックス(プレースホルダー)に移動するかを気にする必要がないため、安全です。
フォン

ところで、実際にはFormatを呼び出します(少なくともRoslynでは)。
フィリップ

ところで、この投稿者が参照しているものは「文字列補間」と呼ばれ、このスレッドの他の場所で扱われています。
CShark 2016年

4
  1. フォーマットは、「。NET」で行う方法です。特定のリファクタリングツール(Refactor!for one)は、フォーマットスタイルを使用するために連結スタイルコードをリファクタリングすることさえ提案します。
  2. 書式設定はコンパイラーに対して最適化する方が簡単です(ただし、2番目はおそらく高速な 'Concat'メソッドを使用するようにリファクタリングされます)。
  3. 書式設定は通常、読みやすくなります(特に「ファンシー」な書式設定の場合)。
  4. 書式設定とは、すべての変数で暗黙的に '.ToString'を呼び出すことを意味します。これは読みやすさの点で優れています。
  5. 「Effective C#」によると、.NETの「WriteLine」と「Format」の実装はめちゃくちゃで、すべての値タイプをオートボックス化します(これは悪いことです)。「Effective C#」は、 '。ToString'呼び出しを明示的に実行することをお勧めします。これは、IMHOが偽物です(Jeffの投稿を参照)。
  6. 現時点では、フォーマットタイプのヒントはコンパイラによってチェックされないため、実行時エラーが発生します。ただし、これは将来のバージョンで修正される可能性があります。

4

読みやすさに基づいて選択します。変数の周りにテキストがある場合は、フォーマットオプションを使用します。この例では:

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

変数名がなくても意味を理解できますが、連結は引用符と+記号で乱雑になり、目を混乱させます。

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(気に入ったのでマイクの例を借りました)

変数名がなければフォーマット文字列があまり意味がない場合は、concatを使用する必要があります。

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

formatオプションを使用すると、変数名を読み取って対応する番号にマッピングできます。concatオプションはそれを必要としません。私はまだ引用符と+記号に戸惑っていますが、別の方法はさらに悪いものです。ルビー?

   Console.WriteLine(p.FirstName + " " + p.LastName);

パフォーマンスに関しては、フォーマットでは文字列を解析する必要があるため、フォーマットオプションは連結よりも遅くなると予想しています。この種の命令を最適化する必要があったことは覚えていませんが、最適化した場合は、およびのstringようなメソッドを調べます。Concat()Join()

formatのもう1つの利点は、フォーマット文字列を構成ファイルに入れることができることです。エラーメッセージとUIテキストで非常に便利です。


4

私はString.Formatを使用しますが、他の言語にローカライズできるように、リソースファイルにもフォーマット文字列を含めます。単純な文字列concatを使用することはできません。明らかに、その文字列をローカライズする必要がない場合、これは考える必要はありません。それは本当に文字列が何のためにあるかに依存します。

ユーザーに表示される場合は、String.Formatを使用して、必要に応じてローカライズできるようにします。念のため、FxCopがスペルチェックを行います。

数値やその他の文字列以外のもの(日付など)が含まれている場合は、書式をより詳細に制御できるため、String.Formatを使用します。

SQLのようなクエリを作成する場合は、Linqを使用します。

ループ内で文字列を連結する場合は、StringBuilderを使用しますをしてパフォーマンスの問題を回避します。

それがユーザーに表示されない何らかの出力用であり、パフォーマンスに影響を与えない場合は、とにかくそれを使用する習慣があり、慣れているため、String.Formatを使用します:)


3

読みやすくする必要があるものを処理している場合(これはほとんどのコードです)、次の場合を除き、演算子のオーバーロードバージョンを使用します。

  • コードは何百万回も実行する必要がある
  • あなたはたくさんの連結をやっています(4以上は1トンです)
  • コードはコンパクトフレームワークを対象としています

これらの状況のうち少なくとも2つでは、代わりにStringBuilderを使用します。


3

結果をローカライズする場合は、String.Formatが不可欠です。これは、異なる自然言語ではデータの順序が同じにならない場合があるためです。


2

これは、出力の複雑さに大きく依存すると思います。その時に最もよく機能するシナリオを選択する傾向があります。

ジョブに基づいて適切なツールを選択します。


2

私も2番目を好みますが、現時点ではその立場を支持する合理的な議論はありません。


2

良いですね!

追加したばかり

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

そして、それはさらに高速です(両方の例でstring.Concatが呼び出されると思いますが、最初の例では何らかの変換が必要です)。

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks

2
演算子ベースの文字列連結がコンパイラによってへの呼び出しに変換されるため、まったく同じ時間がかかりますstring.Concat(...)。コンパイル時に行われるため、実行時のパフォーマンスには影響しません。テストを複数回実行したり、より大きなテストサンプルで実行したりすると、それらは同一であることがわかります。
Allon Guralnek、2011

2

ここでの回答はすべてをカバーしているとは思わないので、ここで少し追加したいと思います。

Console.WriteLine(string format, params object[] pars)を呼び出しますstring.Format。「+」は文字列連結を意味します。これは常にスタイルとは関係がないと思います。状況に応じて、2つのスタイルを混在させる傾向があります。

短い答え

あなたが直面している決定は文字列の割り当てに関係しています。私はそれを簡単にしようとします。

あなたが持っていると言う

string s = a + "foo" + b;

これを実行すると、次のように評価されます。

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmpここは実際にはローカル変数ではありませんが、JITの一時的なものです(ILスタックにプッシュされます)。スタックに文字列をプッシュする場合(ldstrリテラルのIL など)、スタック上の文字列ポインターへの参照を配置します。

あなたが電話した瞬間 concat両方の文字列を含む使用可能な文字列参照がないため、この参照問題になります。これは、.NETが新しいメモリブロックを割り当て、2つの文字列で埋める必要があることを意味します。これが問題である理由は、割り当てが比較的高価であるためです。

質問の変更点:concat操作の数を減らすにはどうすればよいですか?

したがって、大まかな答えは次のとおりstring.Formatです。> 1連結の場合、 '+'は1連結でうまく機能します。マイクロパフォーマンスの最適化を行う必要がないstring.Format場合は、一般的なケースで問題なく機能します。

文化についてのメモ

そして文化と呼ばれるものがあります...

string.FormatCultureInfoフォーマットで使用できます。単純な演算子「+」は現在のカルチャを使用します。

これは、ファイル形式とf.exを記述している場合は特に重要です。double文字列に「追加」する値。別のマシンでstring.Formatは、明示的にを使用しないと、別の文字列になる可能性がありますCultureInfo

F.ex. 「。」を変更するとどうなるかを検討してください。コンマ区切り値ファイルの書き込み中の「、」の場合...オランダ語では、小数点記号はコンマであるため、ユーザーは「おかしい」驚きを感じるだけかもしれません。

より詳細な回答

文字列の正確なサイズが事前にわからない場合は、このようなポリシーを使用して、使用するバッファーを全体的に割り当てることをお勧めします。最初にスラックスペースが埋められ、その後データがコピーされます。

成長とは、新しいメモリブロックを割り当て、古いデータを新しいバッファにコピーすることを意味します。その後、メモリの古いブロックを解放できます。この時点で最終的な結果が得られます。成長はコストのかかる操作です。

これを行う最も実用的な方法は、割り当て超過ポリシーを使用することです。最も一般的なポリシーは、バッファを2の累乗で割り当てることです。もちろん、それより少し賢くする必要があります(128文字が必要なことがすでにわかっている場合は、1、2、4、8から大きくしても意味がないため)。 )しかし、画像を取得します。このポリシーにより、上記で説明した高価な操作をあまり必要としないことが保証されます。

StringBuilder基本的には、基になるバッファーを2の累乗で全体的に割り当てるクラスです。フードの下でstring.Format使用StringBuilderします。

これにより、overlocate-and-append(-multiple)(w / woカルチャ)または単なるallocate-and-appendの間の基本的なトレードオフが決定されます。


1

個人的には、使用しているすべてのものとしての2番目のものは、出力される順序で直接出力されます。1番目のものは、{0}と{1}を適切なvarと一致させる必要があります。

少なくとも、C ++のsprintfほど悪くはありません。変数の型が間違っていると、すべてが爆発します。

また、2番目はすべてインラインであり、すべての{0}の検索や置換を行う必要がないため、後者の方が高速です...確かにわかりません。


1

テキストと混ざっている変数がたくさんあると読みやすくなるので、私は最初のものが好きです。さらに、string.Format()を使用すると、引用符を処理する方が簡単です。文字列連結の適切な分析を次に示します。


1

私はいつもstring.Format()ルートを行ってきました。Nathanの例のように変数にフォーマットを格納できることは大きな利点です。場合によっては、変数を追加することがありますが、1つ以上の変数が連結されると、フォーマットを使用するようにリファクタリングします。


1

ああ、そして完全を期すために、以下は通常の連結よりも数ティック速いです:

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));

1

最初のもの(フォーマット)は私にはよく見えます。より読みやすく、追加の一時的な文字列オブジェクトを作成していません。


1

StringBuilderがこれらのテストのどこに立っているのか気になった。以下の結果...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

結果:

連結:406ティック
連結:356ティック
連結:411ティック
連結:299ティック
連結:266ティック
フォーマット:5269ティック
形式:954ティック
形式:1004ティック
フォーマット:984ティック
フォーマット:974ティック
StringBuilder:629ティック
StringBuilder:484ティック
StringBuilder:482ティック
StringBuilder:508ティック
StringBuilder:504ティック

1

MCSD準備資料によると、Microsoftは、非常に少数の連結(おそらく2から4)を処理するときに+演算子を使用することを推奨しています。理由はまだわかりませんが、検討する必要があります。

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