Excelへのセル入力を決定するためにインデックス値を解析する小さなコードがあります。考えさせられて...
違いは何ですか
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
そして
xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);
どちらか一方が「より良い」ですか?なぜ?
Excelへのセル入力を決定するためにインデックス値を解析する小さなコードがあります。考えさせられて...
違いは何ですか
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
そして
xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);
どちらか一方が「より良い」ですか?なぜ?
回答:
C#6より前
正直に言うと、最初のバージョンの方が単純だと思います。ただし、次のように単純化します。
xlsSheet.Write("C" + rowIndex, null, title);
私は他の回答がパフォーマンスの打撃について話しているのではないかと思いますが、正直に言うとそれがあったとしても最小限です -そして、この連結バージョンはフォーマット文字列を解析する必要はありません。
ローカリゼーションなどの目的でフォーマット文字列は優れていますが、このような場合は連結が単純で、同じように機能します。
C#6を使用
文字列補間により、C#6で多くのことを簡単に読み取ることができます。この場合、2番目のコードは次のようになります。
xlsSheet.Write($"C{rowIndex}", null, title);
これはおそらく最良の選択肢ですIMO。
xlsSheet.Write($"C{rowIndex}", null, title);
私の最初の好み(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()
エッジがあります。string.Format
ReSharperを使用する場合、「安全」です。つまり、[誤って]使用できる他のコードと同じくらい安全です。2)string.Format
ない「安全」を可能にnull
:string.Format("A{0}B", (string)null)
「AB」の結果。3)このレベルのパフォーマンスを気にすることはめったにありません(そして、そのために、私が引っ張るのはまれな日StringBuilder
です)...
string s = "This " + MyMethod(arg) + " is a test";
コンパイルされString.Concat()
ます。
私は最初のオプションがより読みやすく、それがあなたの主な関心事であると思います。
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
string.Formatは内部でStringBuilderを使用するので(反射器で確認)、大量の連結を行わない限り、パフォーマンス上の利点はありません。シナリオの速度は遅くなりますが、実際には、このマイクロパフォーマンス最適化の決定はほとんどの場合不適切であり、ループに入っていない限り、コードの読みやすさに焦点を当てるべきです。
どちらの方法でも、まず読みやすさを優先して記述し、パフォーマンスに懸念があると本当に思われる場合は、パフォーマンスプロファイラーを使用してホットスポットを特定します。
それは単純な単一連結のシンプルなケースでは、私はそれは複雑価値はないと感じて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";
String.Format()
(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)を使用することで削除されるのは、これらの中間結果です。
各中間文字列を作成するには、前の文字列を新しいメモリの場所にコピーする必要があることに注意してください。メモリの割り当てが遅いだけではありません。
その例は、違いに気付くにはおそらく些細なことです。実際、ほとんどの場合、コンパイラーは違いをまったく最適化できないと思います。
ただし、推測しなければならない場合はstring.Format()
、より複雑なシナリオの方が有利です。しかし、それは、実際のデータに基づいていない複数の不変文字列を生成する代わりに、バッファを利用してより良い仕事をする可能性が高いという、より直感的な感覚です。
上記の多くの点に同意します。言及すべきもう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を使用して、文字列を変更するだけです。連結するには、そのメッセージを変更する必要があります
複数の場所で使用すると、時間の割り当てを節約できます。
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秒かかりました。
"1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"
1つの文字列リテラルとして認識するため、前の行"12345678910" + i
よりも速い行が効果的になりますstring.Format(...)
文字列の連結は、String.Formatに比べて多くのメモリを必要とします。文字列を連結する最良の方法は、String.FormatまたはSystem.Text.StringBuilderオブジェクトを使用することです。
最初のケースを考えてみましょう: "C" + rowIndex.ToString()rowIndexが値型であると仮定して、ToString()メソッドがBoxで値を文字列に変換し、CLRが両方の値を含む新しい文字列のメモリを作成する必要があるとします。
string.Formatはオブジェクトパラメータを期待し、rowIndexをオブジェクトとして受け取り、それを内部的に文字列に変換しますが、ボクシングはありますが、それは本質的であり、最初のケースほどメモリを消費しません。
短い文字列の場合、私はそれほど重要ではないと思います...