-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Updated proposal (new)
Edit May 16, 2022 by @GrabYourPitchforks. See #14386 (comment) for further discussion.
namespace System
{
public static partial class MemoryExtensions
{
public static ReadOnlySpan<T> TrimIfStartsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value);
public static ReadOnlySpan<char> TrimIfStartsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType);
public static Span<T> TrimIfStartsWith<T>(this Span<T> span, ReadOnlySpan<T> value);
public static Span<char> TrimIfStartsWith(this Span<char> span, ReadOnlySpan<char> value, StringComparison comparisonType);
public static ReadOnlySpan<T> TrimIfEndsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value);
public static ReadOnlySpan<char> TrimIfEndsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType);
public static Span<T> TrimIfEndsWith<T>(this Span<T> span, ReadOnlySpan<T> value);
public static Span<char> TrimIfEndsWith(this Span<char> span, ReadOnlySpan<char> value, StringComparison comparisonType);
public static ReadOnlySpan<T> TrimIfSurroundedBy<T>(this ReadOnlySpan<T> span, T startValue, T endValue);
public static ReadOnlySpan<T> TrimIfSurroundedBy<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> startValue, ReadOnlySpan<T> endValue);
public static ReadOnlySpan<char> TrimIfSurroundedBy<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> startValue, ReadOnlySpan<T> endValue, StringComparison comparisonType);
public static Span<T> TrimIfSurroundedBy<T>(this Span<T> span, T startValue, T endValue);
public static Span<T> TrimIfSurroundedBy<T>(this Span<T> span, ReadOnlySpan<T> startValue, ReadOnlySpan<T> endValue);
public static Span<char> TrimIfSurroundedBy<T>(this Span<T> span, ReadOnlySpan<T> startValue, ReadOnlySpan<T> endValue, StringComparison comparisonType);
}
}
/*
* n.b. New APIs on CompareInfo are not strictly necessary, since there are existing IsPrefix and IsSuffix methods
* which are suitable. These new APIs would be simple "call IsPrefix / IsSuffix then slice" wrappers. They would
* be the only APIs on CompareInfo which perform manipulation (slicing) in addition to inspection.
*/
namespace System.Globalization
{
public class CompareInfo
{
public string TrimPrefix(string source, string prefix, CompareOptions options = CompareOptions.None);
public string TrimSuffix(string source, string suffix, CompareOptions options = CompareOptions.None);
public static ReadOnlySpan<char> TrimPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options = CompareOptions.None);
public static ReadOnlySpan<char> TrimSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options = CompareOptions.None);
}
}
Updated Proposal (old)
It is useful to have methods that trim a specified prefix or suffix from a string. This is somewhat simple for a developer to write themselves, but it seems to be a common enough request that it would be useful to support directly, especially in regards to robustness and performance.
Here are some references gleaned from the original issue.
http://stackoverflow.com/questions/7170909/trim-string-from-end-of-string-in-net-why-is-this-missing
http://stackoverflow.com/questions/4101539/c-sharp-removing-strings-from-end-of-string
http://stackoverflow.com/questions/5284591/how-to-remove-a-suffix-from-end-of-string
http://stackoverflow.com/questions/4335878/c-sharp-trimstart-with-string-parameter
Usage
There are only 2 overloads for each method, as shown in the following examples:
// Default overload
"http://foo.com".RemoveStart("http://").RemoveEnd(".com") == "foo"
// StringComparison
"http://foo.com".RemoveStart("HTTP://", StringComparison.OrdinalIgnoreCase) == "foo.com"
Background
Api review
Proposed API
namespace System
{
public partial class String
{
/// <summary>
/// Removes the specified prefix if it is found at the start of the string.
/// Uses <see cref="StringComparison.Ordinal"/>.
/// </summary>
/// <param name="prefix">The prefix to remove.</param>
public string RemoveStart(string value);
/// <summary>
/// Removes the specified prefix if it is found at the start of the string.
/// Uses the specified <see cref="StringComparison"/>.
/// </summary>
/// <param name="suffix">The prefix to remove.</param>
/// <param name="comparisonType">The string comparison method to use.</param>
public string RemoveStart(string value, StringComparison comparisonType);
/// <summary>
/// Removes the specified suffix if it is found at the end of the string.
/// Uses <see cref="StringComparison.Ordinal"/>.
/// </summary>
/// <param name="suffix">The suffix to remove.</param>
public string RemoveEnd(string value);
/// <summary>
/// Removes the specified suffix if it is found at the end of the string.
/// Uses the specified <see cref="StringComparison"/>.
/// </summary>
/// <param name="suffix">The suffix to remove.</param>
/// <param name="comparisonType">The string comparison method to use.</param>
public string RemoveEnd(string value, StringComparison comparisonType);
}
// Per @danmosemsft's suggestion
public static partial class MemoryExtensions
{
// Implementation specializes T for byte and char
public static ReadOnlySpan<T> RemoveStart(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
where T : IEquatable<T>;
public static ReadOnlySpan<T> RemoveEnd(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
where T : IEquatable<T>;
// .Fast
public static ReadOnlySpan<char> RemoveStart(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType);
public static ReadOnlySpan<char> RemoveEnd(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType);
}
}
Details, Decisions
(Some of these items from @tarekgh's feedback above)
Naming aligns with existing patterns StartsWith, EndsWith, TrimStart, TrimEnd
Decision: namespace System
Decision: No bool repeat overloads. The callsite can call this recursively (at the risk of more allocations).
Looking that linked StackOverflow questions it seems folks want the non-repeating behavior, i.e. "SIdId".RemoveSuffix("Id") should return "SId", not "S".
We don't want to introduce overloads for TrimEnd and TrimStart that have a non-repeating behavior because it's inconsistent with the existing ones.
At the same time, we feel the bool option is overkill and not very readable from the call site.
Questions
Should be instance methods on String (as opposed to extension methods)?
Should we add corresponding methods to TextInfo (or whatever they care called in globalization)?
If a null value for prefix or suffix is provided, should we throw or just return this?
Old Proposal
The proposal is to add new extension methods / overloads for string trimming,
As you know, right now, it's only possible to trim individual characters from a string, but I would like to trim suffixes & prefixes.
An example usage:
`"MyTest".TrimEnd("Test") == "My" == true`
I feel like they should've be there. It seems kind of weird to me, as I've implemented these by myself in the past few times, and am sure there are quite few people who miss this & would find it useful:
http://stackoverflow.com/questions/7170909/trim-string-from-end-of-string-in-net-why-is-this-missing
http://stackoverflow.com/questions/4101539/c-sharp-removing-strings-from-end-of-string
http://stackoverflow.com/questions/5284591/how-to-remove-a-suffix-from-end-of-string
http://stackoverflow.com/questions/4335878/c-sharp-trimstart-with-string-parameter
Now the following statement is not true, but if it would be, it would describe how I am feeling: "ooh hey, we offer you string replace method which can replace individual chars, but not the whole strings. It's not too hard to craft your own one, give it a try!"
The following applies to TrimEnd
/ TrimStart
, but the overloads would be 1:1, so I will discuss only TrimEnd
.
First overload:
public string TrimEnd(string suffix)
Behaviour: trim the suffix, case-sensitive, and only once. The comparision is done the same way, as it would be for string.Replace
.
"MyTestTest".TrimEnd("Test") == "MyTest" == true
Second overload:
public string TrimEnd(string suffix, StringComparison comparison)
Works as the first one, but allows you to explictly tell how to compare.
"MyTESTtest".TrimEnd("test", StringComparison.InvariantCultureIgnoreCase) == "MyTEST" == true
Third overload(s):
I am not sure if these are needed, but I wanted to throw this out here anyway:
public string TrimEnd(string suffix, bool trimRepeatedly)
public string TrimEnd(string suffix, bool trimRepeatedly StringComparison comparison)
"MyTestTEST".TrimEnd("test", true, StringComparison.InvariantCultureIgnoreCase) == "My" == true
This proposal has nothing to do with string.Trim(), as it would be ambigious. "tetet".Trim("tet") == ???
Namespace: System
Type: System.String
Assembly: System.Runtime.dll
I'd be willing to work on this :3