Edited for the release of .Net Core 2.1
Repeating the test for the release of .Net Core 2.1, I get results like this
1000000 iter
Well, I just wrote up a little test, trying 3 different ways of creating a string from an IEnumerable:
StringBuilder
and repeated invocations of its Append(char ch)
method.string.Concat<T>
String
constructor.10,000 iterations of generating a random 1,000 character sequence and building a string from it, I see the following timings in a release build:
StringBuilder
the clear winner. I'm using a static StringBuilder
(singleton) instance, though. Dunno if that makes much difference.
Here's the source code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace ConsoleApplication6
{
class Program
{
static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create() ;
static readonly byte[] buffer = {0,0} ;
static char RandomChar()
{
ushort codepoint ;
do
{
Random.GetBytes(buffer) ;
codepoint = BitConverter.ToChar(buffer,0) ;
codepoint &= 0x007F ; // restrict to Unicode C0 ;
} while ( codepoint < 0x0020 ) ;
return (char) codepoint ;
}
static IEnumerable<char> GetRandomChars( int count )
{
if ( count < 0 ) throw new ArgumentOutOfRangeException("count") ;
while ( count-- >= 0 )
{
yield return RandomChar() ;
}
}
enum Style
{
StringBuilder = 1 ,
StringConcatFunction = 2 ,
StringConstructor = 3 ,
}
static readonly StringBuilder sb = new StringBuilder() ;
static string MakeString( Style style )
{
IEnumerable<char> chars = GetRandomChars(1000) ;
string instance ;
switch ( style )
{
case Style.StringConcatFunction :
instance = String.Concat<char>( chars ) ;
break ;
case Style.StringBuilder :
foreach ( char ch in chars )
{
sb.Append(ch) ;
}
instance = sb.ToString() ;
break ;
case Style.StringConstructor :
instance = new String( chars.ToArray() ) ;
break ;
default :
throw new InvalidOperationException() ;
}
return instance ;
}
static void Main( string[] args )
{
Stopwatch stopwatch = new Stopwatch() ;
foreach ( Style style in Enum.GetValues(typeof(Style)) )
{
stopwatch.Reset() ;
stopwatch.Start() ;
for ( int i = 0 ; i < 10000 ; ++i )
{
MakeString( Style.StringBuilder ) ;
}
stopwatch.Stop() ;
Console.WriteLine( "Style={0}, elapsed time is {1}" ,
style ,
stopwatch.Elapsed
) ;
}
return ;
}
}
}
It's worth noting that these results, whilst true for the case of IEnumerable from a purists point of view, are not always thus. For example if you were to actually have a char array even if you are passed it as an IEnumerable it is faster to call the string constructor.
The results:
Sending String as IEnumerable<char>
10000 iterations of "new string" took 157ms.
10000 iterations of "sb inline" took 150ms.
10000 iterations of "string.Concat" took 237ms.
========================================
Sending char[] as IEnumerable<char>
10000 iterations of "new string" took 10ms.
10000 iterations of "sb inline" took 168ms.
10000 iterations of "string.Concat" took 273ms.
The Code:
static void Main(string[] args)
{
TestCreation(10000, 1000);
Console.ReadLine();
}
private static void TestCreation(int iterations, int length)
{
char[] chars = GetChars(length).ToArray();
string str = new string(chars);
Console.WriteLine("Sending String as IEnumerable<char>");
TestCreateMethod(str, iterations);
Console.WriteLine("===========================================================");
Console.WriteLine("Sending char[] as IEnumerable<char>");
TestCreateMethod(chars, iterations);
Console.ReadKey();
}
private static void TestCreateMethod(IEnumerable<char> testData, int iterations)
{
TestFunc(chars => new string(chars.ToArray()), testData, iterations, "new string");
TestFunc(chars =>
{
var sb = new StringBuilder();
foreach (var c in chars)
{
sb.Append(c);
}
return sb.ToString();
}, testData, iterations, "sb inline");
TestFunc(string.Concat, testData, iterations, "string.Concat");
}