96

I'm looking for String extension methods for TrimStart() and TrimEnd() that accept a string parameter.

I could build one myself but I'm always interested in seeing how other people do things.

How can this be done?

p.campbell
  • 98,673
  • 67
  • 256
  • 322
Mike Cole
  • 14,474
  • 28
  • 114
  • 194
  • 4
    Your question is not very clear. What should the string parameter do exactly in a trim function? (Assuming that you are not referring to the mandatory 'this string' syntax of the extension method) – asawyer Dec 02 '10 at 14:14
  • 2
    So you want two functions with the same functionality as TrimStart/TrimEnd | Or you want one that will strip the input characters from the start/end? – Blam Dec 02 '10 at 14:15
  • 1
    Yes, same functionality as TrimStart and TrimEnd, but accepts a string instead of a char. – Mike Cole Dec 02 '10 at 14:15
  • String in .Net has Trim, TrimEnd and TrimStart, do you not want to use them? or are you wanting to see how this might be done as a learning exercise? – jwwishart Dec 02 '10 at 14:17

9 Answers9

136

To trim all occurrences of the (exactly matching) string, you can use something like the following:

TrimStart

public static string TrimStart(this string target, string trimString)
{
    if (string.IsNullOrEmpty(trimString)) return target;

    string result = target;
    while (result.StartsWith(trimString))
    {
        result = result.Substring(trimString.Length);
    }

    return result;
}

TrimEnd

public static string TrimEnd(this string target, string trimString)
{
    if (string.IsNullOrEmpty(trimString)) return target;

    string result = target;
    while (result.EndsWith(trimString))
    {
        result = result.Substring(0, result.Length - trimString.Length);
    }

    return result;
}

To trim any of the characters in trimChars from the start/end of target (e.g. "foobar'@"@';".TrimEnd(";@'") will return "foobar") you can use the following:

TrimStart

public static string TrimStart(this string target, string trimChars)
{
    return target.TrimStart(trimChars.ToCharArray());
}

TrimEnd

public static string TrimEnd(this string target, string trimChars)
{
    return target.TrimEnd(trimChars.ToCharArray());
}
Patrick McDonald
  • 64,141
  • 14
  • 108
  • 120
  • 17
    This is not a good solution since it also trims a partial match: "foobartes".TrimEnd("test".ToCharArray()) results in foobar. Imho it should only trim if you get an exact match. edit: quickfix: add a target.EndsWith(trimChars) check before trimming – Contra Mar 28 '11 at 08:40
  • agreed, this doesn't sound like what the op was requesting. I was under the assumption they want to trim a certain string from the beginning of a larger string. Simply TrimStarting all of the chars in the string could remove cases where it does not match exactly. – Tim May 19 '11 at 19:24
  • 2
    Thanks for the `string.ToCharArray()` tip. – Hamid Sadeghian Jan 10 '16 at 11:21
  • 7
    Honestly the part about `TrimStart/End(trimChars.ToCharArray());` should really be deleted. That will pretty much never work as expected except in a naive test and then garble strings in production usage. – Chris Marisic Mar 24 '16 at 14:48
  • 1
    This will loop forever if `trimString == ""`, also not sure you always want `"12123".TimeStart("12") == "3"`. – smg Jan 12 '17 at 08:46
  • @smg agreed, it shouldn't be a while loop, should be a simple "if" instead – goamn Jul 10 '18 at 23:58
  • It should read "if (String.IsNullOrEmpty(trimString)) return target;" – LePatay Dec 17 '18 at 10:33
17

TrimStart and TrimEnd takes in an array of chars. This means that you can pass in a string as a char array like this:

var trimChars = " .+-";
var trimmed = myString.TrimStart(trimChars.ToCharArray());

So I don't see the need for an overload that takes a string parameter.

Rune Grimstad
  • 35,612
  • 10
  • 61
  • 76
  • 1
    Wow, that's embarrassing. I didn't realize that's how it worked. Thanks! – Mike Cole Dec 02 '10 at 14:24
  • 42
    Note that this solution matches the trim chars in **any** order. For example, if `myString="Sammy"` and `trimChars="Sam"` it will return `"y", but the expected result is "my". So it trims too much, and does not regard the trim word as such. – Matt Aug 21 '17 at 11:09
  • 2
    As @Matt explained, this way is a bit risky to do it. This will happen because the Trim(Start/End) take an array of chars as an argument, not a string. Given that example you can also have `myString="Sammy"` and `trimChars="SaX"`; this will result in `"mmy"`. Make sure that's your desired behaviour – Andrew Jan 05 '21 at 14:53
12

I thought the question was trying to trim a specific string from the start of a larger string.

For instance, if I had the string "hellohellogoodbyehello", if you tried to call TrimStart("hello") you would get back "goodbyehello".

If that is the case, you could use code like the following:

string TrimStart(string source, string toTrim)
{
    string s = source;
    while (s.StartsWith(toTrim))
    {
        s = s.Substring(toTrim.Length - 1);
    }
    return s;
}

This wouldn't be super-efficient if you needed to do a lot of string-trimming, but if its just for a few cases, it is simple and gets the job done.

Niederee
  • 4,155
  • 25
  • 38
Tim
  • 1,814
  • 8
  • 29
  • 41
5

To match entire string and not allocate multiple substrings, you should use the following:

    public static string TrimStart(this string source, string value, StringComparison comparisonType)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        int valueLength = value.Length;
        int startIndex = 0;
        while (source.IndexOf(value, startIndex, comparisonType) == startIndex)
        {
            startIndex += valueLength;
        }

        return source.Substring(startIndex);
    }

    public static string TrimEnd(this string source, string value, StringComparison comparisonType)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        int sourceLength = source.Length;
        int valueLength = value.Length;
        int count = sourceLength;
        while (source.LastIndexOf(value, count, comparisonType) == count - valueLength)
        {
            count -= valueLength;
        }

        return source.Substring(0, count);
    }
Fabricio Godoy
  • 517
  • 6
  • 8
  • Thank you, nice code. I just apply a bit changes to it » ‍`if (source == null){return null;}` in my App. I think it is a better choose in this cases. – Ramin Bateni Jun 17 '18 at 01:46
3

from dotnetperls.com,

Performance

Unfortunately, the TrimStart method is not heavily optimized. In specific situations, you will likely be able to write character-based iteration code that can outperform it. This is because an array must be created to use TrimStart.

However: Custom code would not necessarily require an array. But for quickly-developed applications, the TrimStart method is useful.

Community
  • 1
  • 1
rockXrock
  • 3,403
  • 1
  • 25
  • 18
3

There is no built in function in C# - but you can write your own extension methods which I will show you below - they can be used like the builtin ones to trim characters, but here you can use strings to trim.

Note that with IndexOf / LastIndexOf, you can choose if it is case sensitive / culture sensitive or not.

I have implemented the feature "repetitive trims" as well (see the optional parameters).

Usage:

// myStr:   the string that needs to be trimmed
// trimStr: the string to trim from myStr
var trimmed1 = myStr.TrimStart(trimStr); 
var trimmed2 = myStr.TrimEnd(trimStr);
var trimmed3 = myStr.TrimStr(trimStr);
var trimmed4 = myStr.Trim(trimStr);

There is one function TrimStr(..) trimming from the start and from the end of the string, plus three functions implementing .TrimStart(...), .TrimEnd(...) and .Trim(..) for compatibility with the .NET trims:

Try it in DotNetFiddle

public static class Extension
{
    public static string TrimStr(this string str, string trimStr, 
                  bool trimEnd = true, bool repeatTrim = true,
                  StringComparison comparisonType = StringComparison.OrdinalIgnoreCase)
    {
        int strLen;
        do
        {
            strLen = str.Length;
            {
             if (trimEnd)
                {
                  if (!(str ?? "").EndsWith(trimStr)) return str;
                  var pos = str.LastIndexOf(trimStr, comparisonType);
                  if ((!(pos >= 0)) || (!(str.Length - trimStr.Length == pos))) break;
                  str = str.Substring(0, pos);
                }
                else
                {
                  if (!(str ?? "").StartsWith(trimStr)) return str;
                  var pos = str.IndexOf(trimStr, comparisonType);
                  if (!(pos == 0)) break;
                  str = str.Substring(trimStr.Length, str.Length - trimStr.Length);
                }
            }
        } while (repeatTrim && strLen > str.Length);
        return str;
    }

     // the following is C#6 syntax, if you're not using C#6 yet
     // replace "=> ..." by { return ... }

    public static string TrimEnd(this string str, string trimStr, 
            bool repeatTrim = true,
            StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) 
            => TrimStr(str, trimStr, true, repeatTrim, comparisonType);

    public static string TrimStart(this string str, string trimStr, 
            bool repeatTrim = true,
            StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) 
            => TrimStr(str, trimStr, false, repeatTrim, comparisonType);

    public static string Trim(this string str, string trimStr, bool repeatTrim = true,
        StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) 
        => str.TrimStart(trimStr, repeatTrim, comparisonType)
              .TrimEnd(trimStr, repeatTrim, comparisonType);

}

Now you can just use it like

    Console.WriteLine("Sammy".TrimEnd("my"));
    Console.WriteLine("Sammy".TrimStart("Sam"));
    Console.WriteLine("moinmoin gibts gips? gips gibts moin".TrimStart("moin", false));
    Console.WriteLine("moinmoin gibts gips? gips gibts moin".Trim("moin").Trim());

which creates the output

Sam
my
moin gibts gips? gips gibts moin
gibts gips? gips gibts

In the last example, the trim function does a repetitive trim, trimming all occurances of "moin" at the beginning and at the end of the string. The example before just trims "moin" only once from the start.

Matt
  • 25,467
  • 18
  • 120
  • 187
  • Note that TrimStr is used internally by Trim, TrimStart and TrimEnd. – Matt Oct 23 '17 at 08:16
  • The difference is that you can use TrimStr to decide via parameter if you want a trim from the start or from the end of the string. – Matt Oct 23 '17 at 08:17
-1

I'm assuming you mean that, for example, given the string "HelloWorld" and calling the function to 'trim' the start with "Hello" you'd be left with "World". I'd argue that this is really a substring operation as you're removing a portion of the string of known length, rather than a trim operation which removes an unknown length of string.

As such, we created a couple of extension methods named SubstringAfter and SubstringBefore. It would be nice to have them in the framework, but they aren't so you need to implement them yourselves. Don't forget to have a StringComparison parameter, and to use Ordinal as the default if you make it optional.

Greg Beech
  • 133,383
  • 43
  • 204
  • 250
-1

If you did want one that didn't use the built in trim functions for whatever reasons, assuming you want an input string to use for trimming such as " ~!" to essentially be the same as the built in TrimStart with [' ', '~', '!']

public static String TrimStart(this string inp, string chars)
{
    while(chars.Contains(inp[0]))
    {
        inp = inp.Substring(1);
    }

    return inp;
}

public static String TrimEnd(this string inp, string chars)
{
    while (chars.Contains(inp[inp.Length-1]))
    {
        inp = inp.Substring(0, inp.Length-1);
    }

    return inp;
}
Blam
  • 2,888
  • 3
  • 25
  • 39
-2

Function to trim start/end of a string with a string parameter, but only once (no looping, this single case is more popular, loop can be added with an extra param to trigger it) :

public static class BasicStringExtensions
{
    public static string TrimStartString(this string str, string trimValue)
    {
        if (str.StartsWith(trimValue))
            return str.TrimStart(trimValue.ToCharArray());
        //otherwise don't modify
        return str;
    }
    public static string TrimEndString(this string str, string trimValue)
    {
        if (str.EndsWith(trimValue))
            return str.TrimEnd(trimValue.ToCharArray());
        //otherwise don't modify
        return str;
    }
}

As mentioned before, if you want to implement the "while loop" approach, be sure to check for empty string otherwise it can loop forever.

goamn
  • 1,939
  • 2
  • 23
  • 39