title: How do I split a string by strings and include the delimiters using .NET?
body: <p>There are many similar questions, but apparently no perfect match, that's why I'm asking.</p>
  
  <p>I'd like to split a random string (e.g. <code>123xx456yy789</code>) by a list of string delimiters (e.g. <code>xx</code>, <code>yy</code>) and include the delimiters in the result (here: <code>123</code>, <code>xx</code>, <code>456</code>, <code>yy</code>, <code>789</code>).</p>
  
  <p>Good performance is a nice bonus. Regex should be avoided, if possible.</p>
  
  <p><b>Update</b>: I did some performance checks and compared the results (too lazy to formally check them though). The tested solutions are (in random order):</p>
  
  <ol>
  <li><a href="https://stackoverflow.com/questions/2484919/how-do-i-split-a-string-by-strings-and-include-the-delimiters-using-net/2485047#2485047">Gabe</a></li>
  <li><a href="https://stackoverflow.com/questions/2484919/how-do-i-split-a-string-by-strings-and-include-the-delimiters-using-net/2485179#2485179">Guffa</a></li>
  <li><a href="https://stackoverflow.com/questions/2484919/how-do-i-split-a-string-by-strings-and-include-the-delimiters-using-net/2485007#2485007">Mafu</a></li>
  <li><a href="https://stackoverflow.com/questions/2484919/how-do-i-split-a-string-by-strings-and-include-the-delimiters-using-net/2485044#2485044">Regex</a></li>
  </ol>
  
  <p>Other solutions were not tested because either they were similar to another solution or they came in too late.</p>
  
  <p>This is the test code:</p>
  
  <pre><code>class Program
  {
      private static readonly List&lt;Func&lt;string, List&lt;string&gt;, List&lt;string&gt;&gt;&gt; Functions;
      private static readonly List&lt;string&gt; Sources;
      private static readonly List&lt;List&lt;string&gt;&gt; Delimiters;
  
      static Program ()
      {
          Functions = new List&lt;Func&lt;string, List&lt;string&gt;, List&lt;string&gt;&gt;&gt; ();
          Functions.Add ((s, l) =&gt; s.SplitIncludeDelimiters_Gabe (l).ToList ());
          Functions.Add ((s, l) =&gt; s.SplitIncludeDelimiters_Guffa (l).ToList ());
          Functions.Add ((s, l) =&gt; s.SplitIncludeDelimiters_Naive (l).ToList ());
          Functions.Add ((s, l) =&gt; s.SplitIncludeDelimiters_Regex (l).ToList ());
  
          Sources = new List&lt;string&gt; ();
          Sources.Add ("");
          Sources.Add (Guid.NewGuid ().ToString ());
  
          string str = "";
          for (int outer = 0; outer &lt; 10; outer++) {
              for (int i = 0; i &lt; 10; i++) {
                  str += i + "**" + DateTime.UtcNow.Ticks;
              }
              str += "-";
          }
          Sources.Add (str);
  
          Delimiters = new List&lt;List&lt;string&gt;&gt; ();
          Delimiters.Add (new List&lt;string&gt; () { });
          Delimiters.Add (new List&lt;string&gt; () { "-" });
          Delimiters.Add (new List&lt;string&gt; () { "**" });
          Delimiters.Add (new List&lt;string&gt; () { "-", "**" });
      }
  
      private class Result
      {
          public readonly int FuncID;
          public readonly int SrcID;
          public readonly int DelimID;
          public readonly long Milliseconds;
          public readonly List&lt;string&gt; Output;
  
          public Result (int funcID, int srcID, int delimID, long milliseconds, List&lt;string&gt; output)
          {
              FuncID = funcID;
              SrcID = srcID;
              DelimID = delimID;
              Milliseconds = milliseconds;
              Output = output;
          }
  
          public void Print ()
          {
              Console.WriteLine ("S " + SrcID + "\tD " + DelimID + "\tF " + FuncID + "\t" + Milliseconds + "ms");
              Console.WriteLine (Output.Count + "\t" + string.Join (" ", Output.Take (10).Select (x =&gt; x.Length &lt; 15 ? x : x.Substring (0, 15) + "...").ToArray ()));
          }
      }
  
      static void Main (string[] args)
      {
          var results = new List&lt;Result&gt; ();
  
          for (int srcID = 0; srcID &lt; 3; srcID++) {
              for (int delimID = 0; delimID &lt; 4; delimID++) {
                  for (int funcId = 3; funcId &gt;= 0; funcId--) { // i tried various orders in my tests
                      Stopwatch sw = new Stopwatch ();
                      sw.Start ();
  
                      var func = Functions[funcId];
                      var src = Sources[srcID];
                      var del = Delimiters[delimID];
  
                      for (int i = 0; i &lt; 10000; i++) {
                          func (src, del);
                      }
                      var list = func (src, del);
                      sw.Stop ();
  
                      var res = new Result (funcId, srcID, delimID, sw.ElapsedMilliseconds, list);
                      results.Add (res);
                      res.Print ();
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p>As you can see, it was really just a quick and dirty test, but I ran the test multiple times and with different order and the result was always very consistent. The measured time frames are in the range of milliseconds up to seconds for the larger datasets. I ignored the values in the low-millisecond range in my following evaluation because they seemed negligible in practice. Here's the output on my box:</p>
  
  <pre>S 0     D 0     F 3     11ms
  1
  S 0     D 0     F 2     7ms
  1
  S 0     D 0     F 1     6ms
  1
  S 0     D 0     F 0     4ms
  0
  S 0     D 1     F 3     28ms
  1
  S 0     D 1     F 2     8ms
  1
  S 0     D 1     F 1     7ms
  1
  S 0     D 1     F 0     3ms
  0
  S 0     D 2     F 3     30ms
  1
  S 0     D 2     F 2     8ms
  1
  S 0     D 2     F 1     6ms
  1
  S 0     D 2     F 0     3ms
  0
  S 0     D 3     F 3     30ms
  1
  S 0     D 3     F 2     10ms
  1
  S 0     D 3     F 1     8ms
  1
  S 0     D 3     F 0     3ms
  0
  S 1     D 0     F 3     9ms
  1       9e5282ec-e2a2-4...
  S 1     D 0     F 2     6ms
  1       9e5282ec-e2a2-4...
  S 1     D 0     F 1     5ms
  1       9e5282ec-e2a2-4...
  S 1     D 0     F 0     5ms
  1       9e5282ec-e2a2-4...
  S 1     D 1     F 3     63ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 1     F 2     37ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 1     F 1     29ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 1     F 0     22ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 2     F 3     30ms
  1       9e5282ec-e2a2-4...
  S 1     D 2     F 2     10ms
  1       9e5282ec-e2a2-4...
  S 1     D 2     F 1     10ms
  1       9e5282ec-e2a2-4...
  S 1     D 2     F 0     12ms
  1       9e5282ec-e2a2-4...
  S 1     D 3     F 3     73ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 3     F 2     40ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 3     F 1     33ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 1     D 3     F 0     30ms
  9       9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37
  S 2     D 0     F 3     10ms
  1       0**634226552821...
  S 2     D 0     F 2     109ms
  1       0**634226552821...
  S 2     D 0     F 1     5ms
  1       0**634226552821...
  S 2     D 0     F 0     127ms
  1       0**634226552821...
  S 2     D 1     F 3     184ms
  21      0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226
  552821... - 0**634226552821... -
  S 2     D 1     F 2     364ms
  21      0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226
  552821... - 0**634226552821... -
  S 2     D 1     F 1     134ms
  21      0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226
  552821... - 0**634226552821... -
  S 2     D 1     F 0     517ms
  20      0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226
  552821... - 0**634226552821... -
  S 2     D 2     F 3     688ms
  201     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 2     F 2     2404ms
  201     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 2     F 1     874ms
  201     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 2     F 0     717ms
  201     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 3     F 3     1205ms
  221     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 3     F 2     3471ms
  221     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 3     F 1     1008ms
  221     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **
  S 2     D 3     F 0     1095ms
  220     0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6
  34226552821217... **</pre>
  
  <p>I compared the results and this is what I found:</p>
  
  <ul>
  <li>All 4 functions are fast enough for common usage.</li>
  <li>The naive version (aka what I wrote initially) is the worst in terms of computation time.</li>
  <li>Regex is a bit slow on small datasets (probably due to initialization overhead).</li>
  <li>Regex does well on large data and hits a similar speed as the non-regex solutions.</li>
  <li>The performance-wise best seems to be Guffa's version overall, which is to be expected from the code.</li>
  <li>Gabe's version sometimes omits an item, but I did not investigate this (bug?).</li>
  </ul>
  
  <p><b>To conclude this topic, I suggest to use Regex, which is reasonably fast. If performance is critical, I'd prefer Guffa's implementation.</b></p>