Updated on
To remove every whitespace character from a string in C#, new string(source.Where(c => !char.IsWhiteSpace(c)).ToArray()) is the one-liner, and Regex.Replace(source, @"\s", string.Empty) is the one-liner that reads better, although neither of them is the fastest.
Whitespace here means all 25 characters that char.IsWhiteSpace() recognises, and the ordinary space is only one of them. Removing only spaces and removing only the whitespace at the two ends of a string are different jobs with different answers, and we cover both below.
Let’s start.
What Counts as Whitespace in C#?
In C#, whitespace means any of the 25 characters for which char.IsWhiteSpace() returns true, and the Unicode standard fixes that set.
Six of them are control characters: tab, line feed, vertical tab, form feed, carriage return and next line. Seventeen are space separators, and that group runs well past the ordinary space to no-break space, en quad, em space, thin space, narrow no-break space and ideographic space. The last two are the line separator and the paragraph separator.
The three mechanisms this page relies on agree on that set. The regular expression \s matches it, char.IsWhiteSpace() tests it, and String.Split() with a null separator splits on it.
The invisible ones catch people out. A no-break space pasted out of a web page or a Word document looks exactly like a space and survives any code that only removes ' '.
These are all 25, with the code point and Unicode category of each:
| Code point | Name | Category |
|---|---|---|
| U+0009 | Character tabulation (tab) | Control |
| U+000A | Line feed | Control |
| U+000B | Line tabulation (vertical tab) | Control |
| U+000C | Form feed | Control |
| U+000D | Carriage return | Control |
| U+0085 | Next line | Control |
| U+0020 | Space | Space separator |
| U+00A0 | No-break space | Space separator |
| U+1680 | Ogham space mark | Space separator |
| U+2000 | En quad | Space separator |
| U+2001 | Em quad | Space separator |
| U+2002 | En space | Space separator |
| U+2003 | Em space | Space separator |
| U+2004 | Three-per-em space | Space separator |
| U+2005 | Four-per-em space | Space separator |
| U+2006 | Six-per-em space | Space separator |
| U+2007 | Figure space | Space separator |
| U+2008 | Punctuation space | Space separator |
| U+2009 | Thin space | Space separator |
| U+200A | Hair space | Space separator |
| U+202F | Narrow no-break space | Space separator |
| U+205F | Medium mathematical space | Space separator |
| U+3000 | Ideographic space | Space separator |
| U+2028 | Line separator | Line separator |
| U+2029 | Paragraph separator | Paragraph separator |
The list comes from Microsoft Learn’s reference page for Char.IsWhiteSpace, which defines the set as the members of the UnicodeCategory.SpaceSeparator, LineSeparator and ParagraphSeparator categories plus tab, line feed, vertical tab, form feed, carriage return and next line.
Line feed and carriage return are on that list, so every method below removes line breaks along with everything else. Removing line breaks specifically, while keeping the other whitespace, is a separate job.
Use Regex to Remove All Whitespace Characters from a String
Regular expressions are very powerful in finding and replacing characters in a string, and we can easily use them to replace all whitespaces with an empty string.
Let’s create a class RemoveWhitespaceMethods in which we will keep all our different methods to remove whitespaces. Let’s now also create a new static method RemoveWhitespacesUsingStaticRegexClass() within this class.
In this method, let’s create a string variable named source which may contain multiple whitespace characters, and then use the Replace() method to return a new string, where all occurrences of whitespace characters are replaced with the empty string:
public static string RemoveWhitespacesUsingStaticRegexClass(string source)
{
return Regex.Replace(source, @"\s", string.Empty);
}
Now, let’s use this method to remove all whitespace:
var sourceRegex = "\v\tHello World!\r\n";
var resultRegex = RemoveWhitespaceMethods.RemoveWhitespacesUsingStaticRegexClass(sourceRegex);
Console.WriteLine(resultRegex); // prints 'HelloWorld!'
Improving Performance with Source Generators
In our example, we are using the static Regex.Replace method, but if we plan on calling our method repeatedly, we can gain a large performance improvement from the regex source generator. Source-generated regex is the default choice from .NET 7 onwards.
Note: As a legacy fallback, on a framework older than .NET 7, we can gain similar performance by creating the regex with the RegexOptions.Compiled option and caching it. You can see an example of this in the source code for this article.
The [GeneratedRegex] attribute is handled by a C# source generator, which writes the regex code at build time. The method it decorates is partial, and the containing class has to be partial too:
public static partial class RemoveWhitespaceMethods
{
[GeneratedRegex(@"\s")]
public static partial Regex SourceGenRemoveWhitespaceRegex();
public static string RemoveWhitespacesUsingSourceGenRegex(string source)
{
return SourceGenRemoveWhitespaceRegex().Replace(source, string.Empty);
}
}
Use LINQ to Remove All Whitespace Characters from a String
We can also use LINQ to remove all whitespace characters.
Let’s again make a new method called RemoveWhitespacesUsingLinqWithStringConstruct()Â taking a string source as a parameter. Within this method, we use the Where() method from LINQ. Within the Where() method, we pass in an expression that determines whether a given character is whitespace or not using char.IsWhiteSpace().
A common method seen around the internet is to pass the LINQ expression into String.Concat() to combine the non-whitespace characters into a new string. Calling the string constructor directly is about 1.7 times faster on a long string, but it allocates about twice as much memory, as the benchmarks section shows. For our example we take the speed and construct the string directly:
public static string RemoveWhitespacesUsingLinqWithStringConstruct(string source)
{
return new string(source.Where(c => !char.IsWhiteSpace(c)).ToArray());
}
Now, let’s use this method to remove whitespaces in our string:
var sourceLinq = "\v\tHello World!\r\n";
var resultLinq = RemoveWhitespaceMethods.RemoveWhitespacesUsingLinqWithStringConstruct(sourceLinq);
Console.WriteLine(resultLinq); //prints 'HelloWorld!'
Use String.Replace() to Remove All Whitespace Characters from a String
String.Replace() is a straightforward way of replacing all occurrences of a character within a string, with a different character, for instance, to replace a comma with a semicolon. In this case, we want to replace the whitespace characters with an empty string, i.e. with string.Empty.Â
String.Replace() replaces every occurrence of the one character we pass it, so we need one pass per whitespace character, and there are 25 of them. The AllWhitespaceCharacters array holds those 25, the characters listed in the table above.
Let’s create a method called RemoveWhitespacesUsingReplace() which returns a new string with the whitespace characters removed. Keep in mind that this is suboptimal, as the String.Replace() method creates a new object every time, so from a performance and memory usage point of view other methods might be preferred:
public static string RemoveWhitespacesUsingReplace(string source)
{
foreach (var c in AllWhitespaceCharacters)
source = source.Replace(c, string.Empty);
return source;
}
Usage is similar to the examples before:
var sourceReplace = "\v\tHello World!\r\n"; var resultReplace = RemoveWhitespaceMethods.RemoveWhitespacesUsingReplace(sourceReplace); Console.WriteLine(resultReplace); // prints 'HelloWorld!'
Use String.Split() and String.Join() to Remove Whitespaces
The String.Split() method returns a string array whose elements are delimited by a specified string. The String.Join() method takes an array of strings and combines them into a new string. We can combine the two of them to perform a removal of all whitespace characters from a string.
Let’s make a static method called RemoveWhitespacesUsingSplitJoin() to show how we can combine these two methods to accomplish our goal:
public static string RemoveWhitespacesUsingSplitJoin(string source)
{
return String.Join("", source.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries));
}
For the String.Split() method, there are several overloads. In this case, we use Split(String[], StringSplitOptions). We pass in default(string[]) in the String.Split() method because we want to pass in a null for the String[] separator parameter, which is then interpreted as using whitespace characters as delimiters. We also pass StringSplitOptions.RemoveEmptyEntries as the second argument, ensuring that empty entries are removed from the resulting array. We cover the String.Split() overloads and StringSplitOptions in a separate article.
Then, we pass the output of the String.Split() method into the String.Join() method. We want to join the substrings without any space or commas in between, so we pass an empty string as the first argument.Â
Now let’s take a look at our method in action:
var sourceSplitJoin= "\v\tHello World!\r\n";
var resultSplitJoin = RemoveWhitespaceMethods.RemoveWhitespacesUsingSplitJoin(sourceSplitJoin);
Console.WriteLine(resultSplitJoin); //prints 'HelloWorld!'
Use StringBuilder to Remove All Whitespace Characters from a String
This next method takes advantage of the StringBuilder to piece together our new string one character at a time. Since we know that the maximum length of our resultant string is the length of our input string, we can initialize the StringBuilder to our maximum capacity. This will help prevent reallocations within the StringBuilder.
Let’s make a static method called RemoveWhitespacesUsingStringBuilder() to remove all whitespace characters:
public static string RemoveWhitespacesUsingStringBuilder(string source)
{
var builder = new StringBuilder(source.Length);
for (var i = 0; i < source.Length; i++)
{
var c = source[i];
if (!char.IsWhiteSpace(c))
builder.Append(c);
}
return source.Length == builder.Length ? source : builder.ToString();
}
Using the input string source, we create a StringBuilder and then loop over all characters of our source string. If the character does not equal to any whitespace character, we append it to our StringBuilder. After looping over all the characters, we create a new string from our builder.
Now let’s use our method to create a new string without whitespace characters:
var sourceStringBuilder = "\v\tHello World!\r\n"; var resultStringBuilder = RemoveWhitespaceMethods.RemoveWhitespacesUsingStringBuilder(sourceStringBuilder); Console.WriteLine(resultStringBuilder); //prints 'HelloWorld!'
Use a Pooled Array to Remove All Whitespace Characters from a String
This technique is similar to the one involving the StringBuilder, but by making use of the Array Pool, we are able to reduce the memory allocations in our code as well as increase the performance of our application. Let’s create a new method called RemoveWhitespacesUsingArray:
public static string RemoveWhitespacesUsingArray(string source)
{
const int maxStackArray = 256; // if source is small enough, we can avoid heap allocation
if (source.Length < maxStackArray)
return RemoveWhitespacesSpanHelper(source, stackalloc char[source.Length]);
var pooledArray = ArrayPool<char>.Shared.Rent(source.Length);
try
{
return RemoveWhitespacesSpanHelper(source, pooledArray.AsSpan(0, source.Length));
}
finally
{
ArrayPool<char>.Shared.Return(pooledArray);
}
}
private static string RemoveWhitespacesSpanHelper(string source, Span<char> dest)
{
var pos = 0;
foreach (var c in source)
if (!char.IsWhiteSpace(c))
dest[pos++] = c;
return source.Length == pos ? source : new string(dest[..pos]);
}
There are two things to notice in this code. The first is the addition of the helper method RemoveWhitespacesSpanHelper. We added this so that we can gain an additional performance improvement when the source string is less than 256 characters. In that situation, we can use a stackalloc array and avoid heap allocations altogether (with the exception of the final returned string of course). Both paths hand the helper a Span<char>, and our article on Span<T> and why it avoids allocations explains the mechanism. The second important piece is the addition of the ArrayPool. If we rent an array from the pool, we have to be sure to return it.
Now, let’s watch our method work:
var sourceArray = "\v\tHello World!\r\n"; var resultArray = RemoveWhitespaceMethods.RemoveWhitespacesUsingArray(sourceArray); Console.WriteLine(resultArray); // prints "HelloWorld!"
How Do We Remove Only Spaces From a String in C#?
Removing only the space character is a different job from removing all whitespace, and a much simpler one, because a single call to String.Replace() does it:
var result = source.Replace(" ", string.Empty);
That removes every space and leaves tabs, newlines and carriage returns untouched. There is no loop, because Microsoft’s documentation for String.Replace(String, String) says it returns a new string in which “all occurrences of a specified string in the current instance are replaced with another specified string”.
Removing spaces is not the same as removing whitespace. A no-break space, U+00A0, is not the space character, so it survives. Text pasted from a browser or a word processor is full of them, and the usual symptom is a string that still looks wrong after a Replace(" ", "") that appeared to work.
If that is a risk, remove all whitespace instead and accept that tabs and newlines go with it.
How Do We Remove Leading and Trailing Whitespace in C#?
To remove whitespace from the two ends of a string and leave the middle alone, we call String.Trim(). It is the fastest option here by a wide margin, so there is no reason to write our own version:
var result = source.Trim();
String.TrimStart() and String.TrimEnd() do the same job at one end. All three stop at the first character that is not whitespace, so " a b " trims to "a b" with the inner space intact, while TrimStart() gives "a b " and TrimEnd() gives " a b".
Called with no arguments, all three use the same 25-character whitespace set as everything else on this page. Each also takes a params char[] overload when we want to trim a specific set of characters instead.
The next section shows a regular expression doing the same job, and the trimming benchmark further down shows it running 27 times slower than String.Trim() on a 5,328-character string.
Use String.Trim() to Remove Leading and Trailing Whitespace Characters
String.Trim() efficiently removes both the leading and trailing whitespace characters, while all whitespace characters in the middle are unaffected. If we just need to trim whitespace from the front or the back we can use String.TrimStart() or String.TrimEnd().
Let’s look at how we can use this to remove whitespace characters from the beginning and the end of the string while leaving the spaces in between unaffected. As earlier, we put this in a method, in this case TrimWhitespacesUsingStringTrim():Â
public static string TrimWhitespacesUsingStringTrim(string source)
{
return source.Trim();
}
You can see that leading and trailing spaces are gone, but the space in between words is unaffected:
var sourceTrim = "\v\tHello World!\r\n"; var resultTrim = RemoveWhitespaceMethods.TrimWhitespacesUsingStringTrim(sourceTrim); Console.WriteLine(resultTrim); //prints 'Hello World!'
Using Regex to Remove Leading and Trailing Whitespace Characters
For completeness, let’s take a look at how we can use a Regex to trim leading and trailing whitespace:
[GeneratedRegex(@"(^\s+|\s+$)")]
public static partial Regex SourceGenTrimWhitespaceRegex();
public static string TrimWhitespacesUsingSourceGenRegex(string source)
{
return SourceGenTrimWhitespaceRegex().Replace(source, string.Empty);
}
We will see when running our benchmarks, that the regex method of string trimming is not even close to the performance of the built-in String.Trim() method, but it is always good to see that there is another way to do something.Â
Which Way to Remove Whitespace Is Fastest in C#?
On a large string, the StringBuilder loop and the pooled-array method finish within 5% of each other, and everything built on regular expressions or String.Split() is 2.7 to 6.1 times slower than the fastest. On a short string the pooled array wins outright, 1.7 times faster than the StringBuilder loop.
Speed and allocations are separate questions here. String.Split() plus String.Join() allocates about 6.5 times what the pooled-array method does, and the repeated String.Replace() about 3.4 times, while the regular expressions are among the slowest methods and among the leanest.
The removal results below print two of the benchmark’s five inputs, a 134,416-character book and a 79-character sentence, and the pooled-array method takes a stackalloc shortcut below 256 characters. On the 79-character sentence, every method here finishes in under a microsecond.
The exact nanoseconds will differ on another machine, so read the ranking and the ratios, and benchmark real data before optimising a close call.
Benchmarks for Removing Whitespace
First, let’s take a look at the results of running our whitespace removal methods. The benchmark results have been ordered from slowest to fastest within each input. Also, for brevity, the benchmark results have been truncated to two of the five inputs, the 134,416-character book and a 79-character sentence. For full results, you can check out the code associated with the article:
BenchmarkDotNet v0.15.8, Windows 10 (10.0.19045.6466/22H2/2022Update) AMD Ryzen 5 3600 3.60GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK 10.0.302 [Host] : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3 Job-QPGOBO : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3 MinIterationTime=500ms | Method | source | Mean | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |----------------------- |----------------------- |----------------:|--------------:|---------:|---------:|---------:|----------:| | UsingStaticRegexClass | The(...).\r\n [134416] | 2,091,016.54 ns | 38,849.236 ns | 66.4063 | 66.4063 | 66.4063 | 215732 B | | UsingCachedRegex | The(...).\r\n [134416] | 1,161,276.27 ns | 31,528.561 ns | 66.4063 | 66.4063 | 66.4063 | 215754 B | | UsingSplitJoin | The(...).\r\n [134416] | 1,147,741.21 ns | 20,237.012 ns | 185.5469 | 185.5469 | 185.5469 | 1400749 B | | UsingSourceGenRegex | The(...).\r\n [134416] | 940,432.12 ns | 18,882.556 ns | 66.4063 | 66.4063 | 66.4063 | 215754 B | | UsingLinqWithConcat | The(...).\r\n [134416] | 782,869.41 ns | 11,660.234 ns | 66.4063 | 66.4063 | 66.4063 | 215790 B | | UsingReplace | The(...).\r\n [134416] | 719,144.73 ns | 11,096.281 ns | 230.4688 | 230.4688 | 230.4688 | 739176 B | | UsingLinqWithConstruct | The(...).\r\n [134416] | 470,823.78 ns | 5,578.467 ns | 133.3008 | 133.3008 | 133.3008 | 431461 B | | UsingArray | The(...).\r\n [134416] | 360,377.55 ns | 1,902.583 ns | 66.4063 | 66.4063 | 66.4063 | 215702 B | | UsingStringBuilder | The(...).\r\n [134416] | 345,525.34 ns | 1,359.170 ns | 142.5781 | 142.5781 | 142.5781 | 484632 B | | UsingStaticRegexClass | Liber(...)mnis. [79] | 717.71 ns | 12.590 ns | 0.0200 | - | - | 168 B | | UsingCachedRegex | Liber(...)mnis. [79] | 477.32 ns | 9.337 ns | 0.0200 | - | - | 168 B | | UsingReplace | Liber(...)mnis. [79] | 400.04 ns | 6.006 ns | 0.0200 | - | - | 168 B | | UsingSourceGenRegex | Liber(...)mnis. [79] | 335.84 ns | 5.888 ns | 0.0200 | - | - | 168 B | | UsingLinqWithConcat | Liber(...)mnis. [79] | 331.67 ns | 15.949 ns | 0.0305 | - | - | 256 B | | UsingSplitJoin | Liber(...)mnis. [79] | 252.33 ns | 4.137 ns | 0.0801 | - | - | 672 B | | UsingLinqWithConstruct | Liber(...)mnis. [79] | 217.17 ns | 10.719 ns | 0.0467 | - | - | 392 B | | UsingStringBuilder | Liber(...)mnis. [79] | 157.08 ns | 5.780 ns | 0.0477 | - | - | 400 B | | UsingArray | Liber(...)mnis. [79] | 90.98 ns | 3.156 ns | 0.0200 | - | - | 168 B |
We see from the results that the fastest method on the long input is the one that uses a StringBuilder, with our array-backed method a close second, 4% behind. On the 79-character sentence the order reverses, and the array-backed method is 1.7 times faster than the StringBuilder one. On the long input, the next closest method is LINQ with the string constructor, and the repeated calls to String.Replace() are about twice as slow as our array-backed method.
Hopefully, looking at the results helps to reinforce the importance of benchmarking our code. It is easy to find a code snippet that looks very clean and easy to implement, only to find out that we have drastically reduced the performance of our code.
Another thing to notice is the amount of memory allocated. The method using String.Split() and String.Join() allocates about 6.5x more memory than our array-backed method, which allocates the least. In the case of the StringBuilder, we see that it allocates about 2.2x the memory that our array-backed method allocates. This may not be a problem in most cases, but it is something to be aware of when we start thinking about memory pressure and the impact that has on garbage collection.
Benchmarks for Trimming Whitespace
Now let’s look at the trimming benchmarks:
| Method | source | Mean | StdDev | Gen0 | Allocated | |-------------------- |----------------------------- |--------------:|-----------:|-------:|----------:| | UsingSourceGenRegex | \n\n\n\n(...)\t\t\t\t [5328] | 11,443.063 ns | 80.3355 ns | 1.2665 | 10600 B | | UsingStringTrim | \n\n\n\n(...)\t\t\t\t [5328] | 416.918 ns | 27.1844 ns | 1.2655 | 10600 B |
Here we see that there is an absolute and clear winner between our two methods, String.Trim(). It would be virtually impossible for us to craft a method that will beat the built-in String.Trim() method, but as always, it is good to benchmark things to see how they stack up. From this simple benchmark, we see that our Regex based method is way short of the mark and is definitely not something we would want to be calling in place of built-in String.Trim().
The table sets both benchmarks beside what each method removes, with the book as the speed reference:
| Method | Removes | Speed on a large string | Allocations | Use it when |
|---|---|---|---|---|
StringBuilder loop | All whitespace | Fastest | About 2.2x the pooled array | Readable code that is also the fastest on a long string |
Pooled array / stackalloc | All whitespace | 4% behind the StringBuilder loop, and fastest on short strings (1.7x ahead) | Lowest | Short strings, or allocation pressure matters |
LINQ with new string(...) | All whitespace | About 1.4x slower than the fastest | About 2x the pooled array | Readability wins and the strings are short |
String.Replace() in a loop | All whitespace | About 2.1x slower than the fastest | High, about 3.4x the pooled array | Never for all whitespace. One Replace() call is right for one character |
Source-generated Regex | All whitespace | About 2.7x slower than the fastest | Low, within 0.1% of the pooled array | A regex is already in play, or the pattern needs to change |
String.Split() + String.Join() | All whitespace | About 3.3x slower than the fastest | Highest of the eight, about 6.5x the pooled array | Never for this job. Split() exists for splitting |
Cached Regex with RegexOptions.Compiled | All whitespace | About 3.4x slower than the fastest | Low, within 0.1% of the pooled array | Targeting a framework older than .NET 7 |
Regex.Replace() static call | All whitespace | Slowest, about 6.1x slower than the fastest | Low, within 0.1% of the pooled array | Once, in code that runs once |
String.Trim() | Leading and trailing only | Not comparable | One new string, none when there is nothing to trim | The whitespace inside the string must survive |
source.Replace(" ", "") | The space character only | Not comparable | One new string, none when there is no space | Tabs and newlines must survive |
The first two rows cover most code. The StringBuilder loop is the fastest on a long string and easy to read, and the pooled array wins on short strings and allocates the least. LINQ with the string constructor is the most readable of the three, at about 1.4 times the fastest time on the long input.
Conclusion
Every removal method here returns the same string, and on the 134,416-character book the slowest takes 6.1 times as long as the fastest. The StringBuilder loop is fastest on long input, the pooled array wins on short input and allocates the least, and String.Trim() is the call when only the two ends need cleaning. The nanoseconds will differ on another machine, so for a close call like the first two, benchmark real data.
Tested with .NET 10.0.10 and BenchmarkDotNet 0.15.8.

Where is the performance comparison? 🙂
Now, when you say it 🙂