title: Fastest way to join strings with a prefix, suffix and separator
body: <p><strong>UPDATE</strong></p>
  
  <p>Following <a href="https://stackoverflow.com/a/13425111/659190">Mr Cheese's answer</a>, it seems that the</p>
  
  <pre><code>public static string Join&lt;T&gt;(string separator, IEnumerable&lt;T&gt; values)
  </code></pre>
  
  <p>overload of <code>string.Join</code> gets its advantage from the use of the <code>StringBuilderCache</code> class.</p>
  
  <p>Does anybody have any feedback on the correctness or reason of this statement?</p>
  
  <p>Could I write my own,</p>
  
  <pre><code>public static string Join&lt;T&gt;(
      string separator,
      string prefix,
      string suffix,
      IEnumerable&lt;T&gt; values)
  </code></pre>
  
  <p>function that uses the <code>StringBuilderCache</code> class?</p>
  
  <hr>
  
  <p>After submitting <a href="https://stackoverflow.com/a/13341782/659190">my answer to this question</a> I got drawn into some analysis of which would be the best performing answer.</p>
  
  <p>I wrote this code, in a console <code>Program</code> class to test my ideas.</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using System.Text;
  
  class Program
  {
      static void Main()
      {
          const string delimiter = ",";
          const string prefix = "[";
          const string suffix = "]";
          const int iterations = 1000000;
  
          var sequence = Enumerable.Range(1, 10).ToList();
  
          Func&lt;IEnumerable&lt;int&gt;, string, string, string, string&gt;[] joiners =
              {
                  Build,
                  JoinFormat,
                  JoinConcat
              };
  
          // Warmup
          foreach (var j in joiners)
          {
              Measure(j, sequence, delimiter, prefix, suffix, 5);
          }
  
          // Check
          foreach (var j in joiners)
          {
              Console.WriteLine(
                  "{0} output:\"{1}\"",
                  j.Method.Name,
                  j(sequence, delimiter, prefix, suffix));
          }
  
          foreach (var result in joiners.Select(j =&gt; new
                  {
                      j.Method.Name,
                      Ms = Measure(
                          j,
                          sequence,
                          delimiter,
                          prefix,
                          suffix,
                          iterations)
                  }))
          {
              Console.WriteLine("{0} time = {1}ms", result.Name, result.Ms);
          }
  
          Console.ReadKey();
      }
  
      private static long Measure&lt;T&gt;(
          Func&lt;IEnumerable&lt;T&gt;, string, string, string, string&gt; func,
          ICollection&lt;T&gt; source,
          string delimiter,
          string prefix,
          string suffix,
          int iterations)
      {
          var stopwatch = new Stopwatch();
  
          stopwatch.Start();
          for (var i = 0; i &lt; iterations; i++)
          {
              func(source, delimiter, prefix, suffix);
          }
  
          stopwatch.Stop();
  
          return stopwatch.ElapsedMilliseconds;
      }
  
      private static string JoinFormat&lt;T&gt;(
          IEnumerable&lt;T&gt; source,
          string delimiter,
          string prefix,
          string suffix)
      {
          return string.Format(
              "{0}{1}{2}",
              prefix,
              string.Join(delimiter, source),
              suffix);
      }
  
      private static string JoinConcat&lt;T&gt;(
          IEnumerable&lt;T&gt; source,
          string delimiter,
          string prefix,
          string suffix)
      {
          return string.Concat(
              prefix,
              string.Join(delimiter, source),
              suffix);
      }
  
      private static string Build&lt;T&gt;(
          IEnumerable&lt;T&gt; source,
          string delimiter,
          string prefix,
          string suffix)
      {
          var builder = new StringBuilder();
          builder = builder.Append(prefix);
  
          using (var e = source.GetEnumerator())
          {
              if (e.MoveNext())
              {
                  builder.Append(e.Current);
              }
  
              while (e.MoveNext())
              {
                  builder.Append(delimiter);
                  builder.Append(e.Current);
              }
          }
  
          builder.Append(suffix);
          return builder.ToString();
      }
  }
  </code></pre>
  
  <p>running the code, in release configuration, built with optimizations, from the command line I get output like this.</p>
  
  <blockquote>
    <p>...</p>
    
    <p>Build time = 1555ms</p>
    
    <p>JoinFormat time = 1715ms</p>
    
    <p>JoinConcat time = 1452ms</p>
  </blockquote>
  
  <p>The only suprise here (to me) is that the Join-Format combination is the slowest. After considering <a href="https://stackoverflow.com/questions/6785/is-string-format-as-efficient-as-stringbuilder">this answer</a>, this makes a little more sense, the output of the <code>string.Join</code> is being processed by the outer <code>StringBuilder</code> in <code>string.Format</code>, there is an inherent delay with this approach.</p>
  
  <p>After musing, I don't clearly understand how <code>string.Join</code> can be faster. I've read about its use of <code>FastAllocateString()</code> but I don't understand how the buffer can be accurately pre-allocated without calling <code>.ToString()</code> on every member of <code>sequence</code>. Why is the Join-Concat combination faster?</p>
  
  <p>Once I understand that, would it be possible to write my own <code>unsafe string Join</code> function, that takes the extra <code>prefix</code> and <code>suffix</code> parameters and out performs the "safe" alternatives.</p>
  
  <p>I've had several attempts and whilst they work, they are not faster.</p>