Reputation: 8903
I need this all the time and am constantly frustrated that the Trim(), TrimStart() and TrimEnd() functions don't take strings as inputs. You call EndsWith() on a string, and find out if it ends with another string, but then if you want to remove it from the end, you have to do substring hacks to do it (or call Remove() and pray it is the only instance...)
Why is this basic function is missing in .NET? And second, any recommendations for a simple way to implement this (preferably not the regular expression route...)
Upvotes: 90
Views: 80209
Reputation: 28290
Yet another version of TrimSuffix
string extention method with nullable strings and optional StringComparison
parameter:
public static string? TrimSuffix(this string? toTrim, string? suffix, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
{
if (toTrim == null || suffix == null) return toTrim;
while(toTrim.EndsWith(suffix))
{
toTrim = toTrim.Remove(toTrim.LastIndexOf(suffix, comparison));
}
return toTrim;
}
Upvotes: 2
Reputation: 34177
TrimEnd()
(and the other trim methods) accept characters to be trimmed, but not strings. If you really want a version that can trim whole strings then you could create an extension method. For example...
public static string TrimEnd(this string input, string suffixToRemove, StringComparison comparisonType = StringComparison.CurrentCulture)
{
if (suffixToRemove != null && input.EndsWith(suffixToRemove, comparisonType))
{
return input.Substring(0, input.Length - suffixToRemove.Length);
}
return input;
}
This can then be called just like the built in methods.
Upvotes: 74
Reputation: 21709
I recently needed a high performance way to remove single or multiple instances of a string from the start/end of a string. This implementation I came up with is O(n) on the length of the string, avoids expensive allocations, and does not call SubString
at all by using a span.
No Substring
hack! (Well, now that I edited my post).
public static string Trim(this string source, string whatToTrim, int count = -1)
=> Trim(source, whatToTrim, true, true, count);
public static string TrimStart(this string source, string whatToTrim, int count = -1)
=> Trim(source, whatToTrim, true, false, count);
public static string TrimEnd(this string source, string whatToTrim, int count = -1)
=> Trim(source, whatToTrim, false, true, count);
public static string Trim(this string source, string whatToTrim, bool trimStart, bool trimEnd, int numberOfOccurrences)
{
// source.IsNotNull(nameof(source)); <-- guard method, define your own
// whatToTrim.IsNotNull(nameof(whatToTrim)); <-- "
if (numberOfOccurrences == 0
|| (!trimStart && !trimEnd)
|| whatToTrim.Length == 0
|| source.Length < whatToTrim.Length)
return source;
int start = 0, end = source.Length - 1, trimlen = whatToTrim.Length;
if (trimStart)
for (int count = 0; start < source.Length; start += trimlen, count++)
{
if (numberOfOccurrences > 0 && count == numberOfOccurrences)
break;
for (int i = 0; i < trimlen; i++)
if ((source[start + i] != whatToTrim[i] && i != trimlen) || source.Length - start < trimlen)
goto DONESTART;
}
DONESTART:
if (trimEnd)
for (int count = 0; end > -1; end -= trimlen, count++)
{
if (numberOfOccurrences != -1 && count == numberOfOccurrences)
break;
for (int i = trimlen - 1; i > -1; --i)
if ((source[end - trimlen + i + 1] != whatToTrim[i] && i != 0) || end - start + 1 < trimlen)
goto DONEEND;
}
DONEEND:
return source.AsSpan().Slice(start, end - start + 1).ToString();
}
Upvotes: 1
Reputation: 70369
EDIT - wrapped up into a handy extension method:
public static string TrimEnd(this string source, string value)
{
if (!source.EndsWith(value))
return source;
return source.Remove(source.LastIndexOf(value));
}
so you can just do s = s.TrimEnd("DEF");
Upvotes: 110
Reputation: 29993
Here's the extension method I came up with (heavy inspiration taken from existing answers to this question) to complement the existing TrimEnd
method; it takes an optional bool allowing for only removing one trailing instance of the string instead of all trailing instances.
/// <summary>
/// Removes trailing occurrence(s) of a given string from the current System.String object.
/// </summary>
/// <param name="trimSuffix">A string to remove from the end of the current System.String object.</param>
/// <param name="removeAll">If true, removes all trailing occurrences of the given suffix; otherwise, just removes the outermost one.</param>
/// <returns>The string that remains after removal of suffix occurrence(s) of the string in the trimSuffix parameter.</returns>
public static string TrimEnd(this string input, string trimSuffix, bool removeAll = true) {
while (input != null && trimSuffix != null && input.EndsWith(trimSuffix)) {
input = input.Substring(0, input.Length - trimSuffix.Length);
if (!removeAll) {
return input;
}
}
return input;
}
Upvotes: 0
Reputation: 4922
Using Daniel's code and wrapping it in a while rather than a straight if
gives functionality more akin to the Microsoft Trim
function:
public static string TrimEnd(this string input, string suffixToRemove)
{
while (input != null && suffixToRemove != null && input.EndsWith(suffixToRemove))
{
input = input.Substring(0, input.Length - suffixToRemove.Length);
}
return input;
}
Upvotes: 13
Reputation: 133995
As far as why the function you want is missing, I suspect it's because the designers didn't see it to be as common or as basic as you think it is. And as you've seen from the other answers, it's an easy enough thing to duplicate.
Here's the method I use to do it.
public static string TrimEnd(string input, string suffixToRemove)
{
if (input == null)
throw new ArgumentException("input cannot be null.");
if (suffixToRemove == null)
throw new ArgumentException("suffixToRemove cannot be null.");
int pos = input.LastIndexOf(suffixToRemove);
if (pos == (input.Length - suffixToRemove.Length))
return input.Substring(0, pos);
return input;
}
Upvotes: -1
Reputation: 1375
I like my TrimEnd to remove all the instances of the string at the end and not just the last one.
public static string TrimEnd(this string str, string trimStr)
{
if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(trimStr)) return str;
while(str.EndsWith(trimStr))
{
str = str.Remove(str.LastIndexOf(trimStr));
}
return str;
}
Upvotes: 3
Reputation: 19601
I knocked up this quick extension method.
Not positive it works (I can't test it right now), but the theory is sound.
public static string RemoveLast(this string source, string value)
{
int index = source.LastIndexOf(value);
return index != -1 ? source.Remove(index, value.Length) : source;
}
Upvotes: 7
Reputation: 104040
Trim(), TrimStart() and TrimEnd() are methods which replace all occurrences of the same character. That means you can only remove a series of blanks or a series of dots for example.
You could use a regular expression replace in order to accomplish this:
string s1 = "This is a sentence.TRIMTHIS";
string s2 = System.Text.RegularExpressions.Regex.Replace(s1, @"TRIMTHIS$", "");
You could wrap it in an extension method for convenience:
public static string TrimStringEnd(this string text, string removeThis)
{
return System.Text.RegularExpressions.Regex.Replace(s1, removeThis, "");
}
And call it this way
string s2 = (@"This is a sentence.TRIMTHIS").TrimStringEnd(@"TRIMTHIS");
Upvotes: 0
Reputation: 32484
Regex replace may be your friend in this instance.
var str = "Hello World!";
str = Regex.Replace(str, @"World!$", "");
//str == "Hello"
Upvotes: 4
Reputation: 22492
This is what you object to having to do?
if (theString.endsWith(theOtherString))
{
theString = theString.Substring(0, theString.Length - theOtherString.Length);
}
Upvotes: 4