【C#】文字列結合の負荷について検証

# 概要

C#には文字列を結合する方法がいくつかあります。
今回はそのうち5つを試してみたので、その結果を記載します。

## 検証した方法
・+演算子
・StringBuilder
・文字列補間→$"Name:{name}"など([$ - 文字列補間 - C# リファレンス | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/tokens/interpolated))
・string.Format
・String.Concat

# 検証の設定
・それぞれの方法を100,000,000回(1億回)繰り返し、処理時間を計測
・System.Diagnostics.Stopwatchクラスを使用して繰り返し文の前後で
　スタート・ストップをして時間を出力


# 検証

以下の関数を実行して計測

``` C:StringMixTestA
	private static void StringMixTestA()
	{
		Console.WriteLine("--- 計測開始A ---");

		string strA = "TestStringA";
		string strB = "TestStringB";
		var stopWatch = new System.Diagnostics.Stopwatch();

		// 通常の"a" + "b";
		stopWatch.Start();
		for (int i = 0; i < testCount; i++)
		{
			var result = "Test A:" + strA + " B:"+ strB;
		}
		stopWatch.Stop();
		Console.WriteLine("Plus：\t\t" + stopWatch.Elapsed.ToString());

		// StringBuilderのAppend"a" + "b";
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Test A:");
			stringBuilder.Append(strA);
			stringBuilder.Append(" B:");
			stringBuilder.Append(strB);
		}
		stopWatch.Stop();
		Console.WriteLine("StringBuilder：\t" + stopWatch.Elapsed.ToString());

		// 文字列補間
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = $"Test A:{strA} Test B:{strB}";
		}
		stopWatch.Stop();
		Console.WriteLine("$：\t\t" + stopWatch.Elapsed.ToString());

		// string.Format()
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = string.Format("Test A:{0} Test B:{1}", strA, strB);
		}
		stopWatch.Stop();
		Console.WriteLine("string.Format：\t" + stopWatch.Elapsed.ToString());

		// String.Concat
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = String.Concat("Test A:", strA, " Test B:", strB);
		}
		stopWatch.Stop();
		Console.WriteLine("String.Concat：\t" + stopWatch.Elapsed.ToString());

		Console.WriteLine("--- 計測終了A ---");
	}
```

## 検証結果

|方法|計測時間(時:分:秒)|
|----|-----|
|Plus|00:00:08.8524457|
|StringBuilder|00:00:16.8160575|
|$|00:00:08.7606260|
|string.Format|00:00:25.5426815|
|String.Concat|00:00:08.6294175|

というわけでString.Concatが一番早いようですが、
調べてみるとStringBuilderが早いという記事もあったので
別パターンも書いて計測してみました。


# 検証B

以下の関数を実行して計測。
先に変数を定義し、+=で結合する形に変更。
※StringBuilderは初めの検証と同じ

``` C:StringMixTestB
	private static void StringMixTestB()
	{
		Console.WriteLine("--- 計測開始B ---");

		string strA = "TestStringA";
		string strB = "TestStringB";
		var stopWatch = new System.Diagnostics.Stopwatch();

		// 通常の"a" + "b";
		stopWatch.Start();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result += "Test A:" + strA + " B:" + strB;
		}
		stopWatch.Stop();
		Console.WriteLine("Plus：\t\t" + stopWatch.Elapsed.ToString());

		// StringBuilderのAppend"a" + "b";
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Test A:");
			stringBuilder.Append(strA);
			stringBuilder.Append(" B:");
			stringBuilder.Append(strB);
		}
		stopWatch.Stop();
		Console.WriteLine("StringBuilder：\t" + stopWatch.Elapsed.ToString());

		// 文字列補間
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result += $"Test A:{strA} Test B:{strB}";
		}
		stopWatch.Stop();
		Console.WriteLine("$：\t\t" + stopWatch.Elapsed.ToString());

		// string.Format()
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result += string.Format("Test A:{0} Test B:{1}", strA, strB);
		}
		stopWatch.Stop();
		Console.WriteLine("string.Format：\t" + stopWatch.Elapsed.ToString());

		// String.Concat
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result += String.Concat("Test A:", strA, " Test B:", strB);
		}
		stopWatch.Stop();
		Console.WriteLine("String.Concat：\t" + stopWatch.Elapsed.ToString());

		Console.WriteLine("--- 計測終了B ---");
	}
```

## 検証結果B

|方法|計測時間(時:分:秒)|
|----|-----|
|Plus|00:00:17.1915427|
|StringBuilder|00:00:16.7240321|
|$|00:00:17.1817604|
|string.Format|00:00:26.1338767|
|String.Concat|00:00:17.0150580|

この結果ではStringBuilderが一番早いですね。
すでに文字列があり、そこに足していくような場合はStringBuilderが良さそうです。


# 検証C

以下の関数を実行して計測。
先に変数を定義し、そのまま代入する形に変更。
※StringBuilderは初めの検証と同じ

``` C:StringMixTestC
	private static void StringMixTestC()
	{
		Console.WriteLine("--- 計測開始C ---");

		string strA = "TestStringA";
		string strB = "TestStringB";
		var stopWatch = new System.Diagnostics.Stopwatch();

		// 通常の"a" + "b";
		stopWatch.Start();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result = "Test A:" + strA + " B:" + strB;
		}
		stopWatch.Stop();
		Console.WriteLine("Plus：\t\t" + stopWatch.Elapsed.ToString());

		// StringBuilderのAppend"a" + "b";
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Test A:");
			stringBuilder.Append(strA);
			stringBuilder.Append(" B:");
			stringBuilder.Append(strB);
		}
		stopWatch.Stop();
		Console.WriteLine("StringBuilder：\t" + stopWatch.Elapsed.ToString());

		// 文字列補間
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result = $"Test A:{strA} Test B:{strB}";
		}
		stopWatch.Stop();
		Console.WriteLine("$：\t\t" + stopWatch.Elapsed.ToString());

		// string.Format()
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result = string.Format("Test A:{0} Test B:{1}", strA, strB);
		}
		stopWatch.Stop();
		Console.WriteLine("string.Format：\t" + stopWatch.Elapsed.ToString());

		// String.Concat
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result = String.Concat("Test A:", strA, " Test B:", strB);
		}
		stopWatch.Stop();
		Console.WriteLine("String.Concat：\t" + stopWatch.Elapsed.ToString());

		Console.WriteLine("--- 計測終了C ---");
	}
```

## 検証結果C

|方法|計測時間(時:分:秒)|
|----|-----|
|Plus|00:00:08.2999059|
|StringBuilder|00:00:16.2361791|
|$|00:00:08.5158497|
|string.Format|00:00:25.3592823|
|String.Concat|00:00:08.5106941|

この場合は+演算子が一番早かったですが、変数展開やString.Concatとは誤差程度ですね。



# 検証D

以下の関数を実行して計測。
StringBuilderが次々追加するのに対し、
Plusがいっぺんに合成しているのが不公平だと思ったので
+で一つの要素ごとに追加するようにしてみた。

``` C:StringMixTestC
	private static void StringMixTestD()
	{
		Console.WriteLine("--- 計測開始D ---");

		string strA = "TestStringA";
		string strB = "TestStringB";
		var stopWatch = new System.Diagnostics.Stopwatch();

		// 通常の"a" + "b";
		stopWatch.Start();
		for (int i = 0; i < testCount; i++)
		{
			var result = "";
			result += "Test A:";
			result += strA;
			result += " B:";
			result += strB;
		}
		stopWatch.Stop();
		Console.WriteLine("Plus：\t\t" + stopWatch.Elapsed.ToString());

		// StringBuilderのAppend"a" + "b";
		stopWatch.Restart();
		for (int i = 0; i < testCount; i++)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Test A:");
			stringBuilder.Append(strA);
			stringBuilder.Append(" B:");
			stringBuilder.Append(strB);
		}
		stopWatch.Stop();
		Console.WriteLine("StringBuilder：\t" + stopWatch.Elapsed.ToString());

		Console.WriteLine("--- 計測終了D ---");
	}
```

## 検証結果D

|方法|計測時間(時:分:秒)|
|----|-----|
|Plus|00:00:16.1928413|
|StringBuilder|00:00:15.2582835|

何度か合成を行う場合はStringBuilderの方が早いということがわかりました。


# まとめ

例えば"100.0kg"のような数値+単位を表すような決まった文字列の場合は
速度の面で言えば+演算子、変数展開、String.Concatがよいでしょう。
より可読性が高いのは変数展開だと私は思っているので
文字列補間をおすすめしたいです。

何度も同じ変数に文字列を足していくような処理の場合は
StringBuilderがよさそうです。