あなたの質問に対する最初の 2 つの回答は、まったく正しくありません。sb.Append(i + ",");
ステートメントは を呼び出しません。i.ToString()
実際に行うことは
StringBuilder.Append(string.Concat((object)i, (object)","));
string.Concat
関数の内部では、渡されToString()
た 2 つobject
の が呼び出されます。このステートメントで重要なパフォーマンスの問題は です(object)i
。これはボクシングです - 値の型を参照内にラップします。これは、何かをボックス化するために余分なサイクルとメモリ割り当てが必要であり、余分なガベージ コレクションが必要になるため、(比較的) かなりのパフォーマンス ヒットです。
これは、(リリース) コンパイル済みコードの IL で確認できます。
IL_000c: box [mscorlib]System.Int32
IL_0011: ldstr ","
IL_0016: call string [mscorlib]System.String::Concat(object,
object)
IL_001b: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
最初の行がbox
call であり、その後に call が続き、Concat
finally calling で終わることを確認してAppend
ください。
以下に示すように、代わりにコールi.ToString()
すると、ボクシングとstring.Concat()
コールを放棄します。
for (int i = 0; i < 50; i++)
{
sb.Append(i.ToString());
sb.Append(",");
}
この呼び出しにより、次の IL が生成されます。
IL_000b: ldloca.s i
IL_000d: call instance string [mscorlib]System.Int32::ToString()
IL_0012: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
IL_0017: pop
IL_0018: ldloc.0
IL_0019: ldstr ","
IL_001e: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
ボクシングも もありませんString.Concat
。したがって、収集する必要がある生成されるリソースが少なくなり、ボクシングで浪費されるサイクルが少なくなりますが、1 つのAppend()
呼び出しを追加するコストがかかります。これは比較的安価です。
これが、2 番目のコード セットの方がパフォーマンスが優れている理由です。
この考えを他の多くのものに拡張できます-値の型を引数として明示的にとらない関数に渡す文字列を操作している場所ならどこでも(たとえばobject
、を引数として取る呼び出し) 、値型の引数を渡すときstring.Format()
に呼び出すことをお勧めします。<valuetype>.ToString()
コメントでのテオドロスの質問に応えて:
コンパイラ チームは確かにそのような最適化を行うことを決定できましたが、コスト (追加の複雑さ、時間、追加のテストなどの観点から) により、そのような変更の価値が投資に値しないと判断したと推測されます。
基本的に、表面上はstring
s で動作するが、その中でオーバーロードを提供するobject
(基本的にはif (boxing occurs && overload has string)
) 関数の特別なケースの分岐を配置する必要がありました。そのブランチ内で、コンパイラは、引数の呼び出しを除いて、object
関数のオーバーロードがオーバーロードと同じことを行うことを確認するためにもチェックする必要があります。これを行う必要があるのは、ユーザーが 1 つの関数がもう 1 つは を受け取りますが、2 つのオーバーロードは引数に対して異なる処理を実行します。string
ToString()
string
object
これは、いくつかの文字列操作関数にマイナーな最適化を行うための多くの複雑さと分析のように思えます。さらに、これは、人々が常に誤解している非常に正確なルールがすでにいくつかあるコアコンパイラ関数解決コードをいじることになります(多くの Eric Lippert の回答を見てください-かなりの数が関数解決の問題を中心に展開しています)。「その状況を除いて、このように機能する」タイプのルールでより複雑にすることは、リターンが最小限である場合は確かに避けるべきものです。
より安価で複雑でない解決策は、基本関数の解決規則を使用し、値の型 ( などint
) を関数に渡すことをコンパイラに解決させ、それに適合する唯一の関数シグネチャが 1 つであることをコンパイラに判断させることです。それがかかりobject
、ボックスを行います。次に、ユーザーが自分のコードをプロファイリングし、それが必要であると判断したときに最適化を行うように頼りますToString()
(または、この動作について知っていて、状況に遭遇したときに常にそれを実行します)。
彼らが行うことができた可能性の高い代替手段は、s、sなど(のような)を取り、ボックス化されない内部で引数を呼び出すだけの多数のstring.Concat
オーバーロードを持つことです。これには、最適化がコンパイラではなくクラス ライブラリにあるという利点がありますが、元の質問のように、連結で型を混在させたい状況に必然的に遭遇します。順列は爆発するだろう。それが彼らがそうしなかった理由である可能性が高い. 彼らはまた、そのようなオーバーロードが使用される最も一般的に使用される状況を決定し、上位 5 を実行することもできたでしょうが、私は彼らが、人々が「まあ、そうしましたが、なぜそうしないのか?」。int
double
string.Concat(int, int)
ToString
string.Concat(int, string)
(int, string)
(string, int)