If you’re coming from another programming language like JavaScript, Rust or Go, you may be familiar with the repeat
function found in the standard library of those languages. Unfortunately, C# doesn’t have this function - atleast as of .NET 6 - so we need to write our own. Here are some examples of how to repeat a string N times in C#.
Using LINQ
public static string RepeatLinq(this string text, uint n)
{
return string.Concat(System.Linq.Enumerable.Repeat(text, (int)n));
}
Here we’re using the Enumerable.Repeat
method from the System.Linq
namespace to repeat the string n
times. We then use the string.Concat
method to concatenate the repeated strings together. This is a very simple way to repeat a string, but it’s not the most efficient way to do it.
Using StringBuilder
public static string RepeatStrBuilder(this string text, uint n)
{
return new StringBuilder(text.Length * (int)n)
.Insert(0, text, (int)n)
.ToString();
}
For this one we instantiate a new StringBuilder
object with the length of the string multiplied by the number of times we want to repeat it. Then we use the Insert
method to insert the string n
times into the StringBuilder
, starting at the beginning of the string (index 0). Finally, we use the ToString
method to return the string.
This method is generally more efficient than the previous one, is relatively simple and concise, but it’s still not the most efficient way to repeat a string.
Using a for loop over an Array of characters
public static string RepeatArray(this string text, uint n)
{
var arr = new char[text.Length * (int)n];
for (var i = 0; i < n; i++)
{
text.CopyTo(0, arr, i * text.Length, text.Length);
}
return new string(arr);
}
Ok, now things are getting interesting. We’re going to use a for loop to repeat the string n
times. We’ll use a char[]
array to store the repeated string. Then we’ll use the CopyTo
method to copy the string into the array. This is a more complicated way of repeating a string, but it can be more effecient than the previous one, especially when n < 100
. But we can do even better.
Using a for loop over a Span of characters
public static string Repeat(this string text, uint n)
{
var textAsSpan = text.AsSpan();
var span = new Span<char>(new char[textAsSpan.Length * (int)n]);
for (var i = 0; i < n; i++)
{
textAsSpan.CopyTo(span.Slice((int)i * textAsSpan.Length, textAsSpan.Length));
}
return span.ToString();
}
Using a Span<char>
we are dropping down a level of abstraction and essentially dealing with contiguous memory on the stack instead of strings. This is why this method is the most efficient of all the ones we’ve seen previously. We’re copying memory slices of the original string into the a new Span<char>
. This turns out to be pretty efficient because it’s not allocating memory on the heap.
This is the method that we chose to use in the Ardalis.Extensions nuget package. You can see the source code on Github.
Here’s how easy it is to use the Repeat
method. Go ahead and try it out!
Benchmarks
Here are the benchmark results of the different methods we’ve used to repeat a string.
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.22000
12th Gen Intel Core i9-12900K, 1 CPU, 24 logical and 16 physical cores
.NET Core SDK=6.0.303
[Host] : .NET Core 6.0.8 (CoreCLR 6.0.822.36306, CoreFX 6.0.822.36306), X64 RyuJIT
DefaultJob : .NET Core 6.0.8 (CoreCLR 6.0.822.36306, CoreFX 6.0.822.36306), X64 RyuJIT
Method | N | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
RepeatLinq | 1 | 15.575 ns | 0.1010 ns | 0.0844 ns | 1.00 | 0.00 | 0.0025 | - | - | 40 B |
RepeatStrBuilder | 1 | 17.407 ns | 0.3854 ns | 0.5885 ns | 1.11 | 0.04 | 0.0071 | - | - | 112 B |
RepeatArray | 1 | 9.459 ns | 0.2053 ns | 0.1920 ns | 0.61 | 0.01 | 0.0041 | - | - | 64 B |
RepeatSpan | 1 | 9.852 ns | 0.2188 ns | 0.3715 ns | 0.63 | 0.03 | 0.0041 | - | - | 64 B |
RepeatLinq | 10 | 71.550 ns | 0.9862 ns | 0.8742 ns | 1.00 | 0.00 | 0.0081 | - | - | 128 B |
RepeatStrBuilder | 10 | 52.637 ns | 1.1027 ns | 3.2515 ns | 0.75 | 0.03 | 0.0142 | - | - | 224 B |
RepeatArray | 10 | 40.338 ns | 0.7593 ns | 0.8744 ns | 0.56 | 0.01 | 0.0112 | - | - | 176 B |
RepeatSpan | 10 | 35.393 ns | 0.7495 ns | 0.7011 ns | 0.49 | 0.01 | 0.0112 | - | - | 176 B |
RepeatLinq | 100 | 554.149 ns | 4.2416 ns | 3.9676 ns | 1.00 | 0.00 | 0.0420 | - | - | 664 B |
RepeatStrBuilder | 100 | 274.108 ns | 5.3881 ns | 5.9889 ns | 0.49 | 0.01 | 0.0825 | - | - | 1296 B |
RepeatArray | 100 | 282.168 ns | 4.4919 ns | 4.2017 ns | 0.51 | 0.01 | 0.0792 | - | - | 1248 B |
RepeatSpan | 100 | 215.679 ns | 3.8950 ns | 3.6434 ns | 0.39 | 0.01 | 0.0794 | 0.0002 | - | 1248 B |
RepeatLinq | 1000 | 5,296.572 ns | 100.2936 ns | 93.8147 ns | 1.00 | 0.00 | 0.3815 | 0.0076 | - | 6064 B |
RepeatStrBuilder | 1000 | 2,477.808 ns | 49.4452 ns | 50.7766 ns | 0.47 | 0.01 | 0.7668 | 0.0267 | - | 12096 B |
RepeatArray | 1000 | 2,755.265 ns | 52.9493 ns | 72.4775 ns | 0.52 | 0.02 | 0.7668 | 0.0153 | - | 12048 B |
RepeatSpan | 1000 | 2,048.442 ns | 39.6767 ns | 55.6213 ns | 0.39 | 0.01 | 0.7668 | 0.0153 | - | 12048 B |