Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overloads for string.Remove() #59607

Closed
1 task done
Bond-Addict opened this issue Dec 23, 2024 · 3 comments
Closed
1 task done

Overloads for string.Remove() #59607

Bond-Addict opened this issue Dec 23, 2024 · 3 comments
Labels
needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically

Comments

@Bond-Addict
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

Currently, when we need to perform replacements in strings, we rely on .Replace(), which requires either a specific string value (string.Replace()) or a regular expression (new Regex.Replace()). While this works, it feels like there would be a benefit to having additional overrides for string.Remove() to handle these scenarios, rather than just the current option that requires specifying a starting and ending index. While the existing approach has its use cases, removing parts of strings—especially with patterns or substrings—feels like a more common need and would provide more flexibility.

Describe the solution you'd like

Below is an extension method class to server as a working example, but of course it's up to the team's interpretation as to how would be the best to implement it.

using System.Text.RegularExpressions;

namespace TestConsoleApp.Extensions
{
    public static class CommonExtensions
    {
        // Remove string or regex pattern from the string, with an optional comparison rule and regex flag
        public static string Remove(this string sourceString, string stringToRemove, bool isRegex = false, StringComparison comparisonRule = StringComparison.Ordinal)
        {
            if (isRegex)
            {
                return sourceString.Remove(new Regex(stringToRemove));
            }
            return sourceString.Replace(stringToRemove, "", comparisonRule);
        }

        // Remove a collection of strings or regex patterns
        public static string Remove(this string sourceString, string stringToRemove, char delimitedCharacter, bool isRegex = false, StringComparison comparisonRule = StringComparison.Ordinal)
        {
            foreach (var partToRemove in stringToRemove.Split(delimitedCharacter))
            {
                sourceString = sourceString.Remove(partToRemove, isRegex, comparisonRule);
            }
            return sourceString;
        }

        public static string Remove(this string sourceString, IEnumerable<string> stringsToRemove, bool isRegex = false, StringComparison comparisonRule = StringComparison.Ordinal)
        {
            foreach (var partToRemove in stringsToRemove)
            {
                sourceString = sourceString.Remove(partToRemove, isRegex, comparisonRule);
            }
            return sourceString;
        }

        // Remove a single regex pattern
        public static string Remove(this string sourceString, Regex removalRegex) => removalRegex.Replace(sourceString, "");

        // Remove a single regex pattern with custom options
        public static string Remove(this string sourceString, Regex removalRegex, Func<RegexOptions> options) =>
            new Regex(removalRegex.ToString(), options()).Replace(sourceString, "");

        // Remove multiple regex patterns
        public static string Remove(this string sourceString, IEnumerable<Regex> regexPatternsToRemove)
        {
            foreach (Regex regexPattern in regexPatternsToRemove)
            {
                sourceString = sourceString.Remove(regexPattern);
            }
            return sourceString;
        }

        // Remove multiple regex patterns with custom options
        public static string Remove(this string sourceString, IEnumerable<Regex> regexPatternsToRemove, Func<RegexOptions> options)
        {
            foreach (Regex regexPattern in regexPatternsToRemove)
            {
                sourceString = sourceString.Remove(regexPattern, options);
            }
            return sourceString;
        }
    }
}

Additional context

There are many situations where we need to sanitize a string for various reasons. Instead of writing and maintaining minimal but still duplicated code for sanitization, it would be far more efficient to simply "Remove" whatever values we don't want. The ability to provide either a single Regex or string, or a collection of Regex or string values to remove from the source string, would simplify this process. This feature would be particularly useful for quick and easy sanitization of usernames, passphrases, or any other user input.

For example, Ally Financial provides an API endpoint (https://secure.ally.com/assets/json/invalid-strings.json) that returns a list of words or phrases they do not allow in usernames or passphrases. Note: This list includes terms that are explicitly filtered for user-provided content and may contain offensive or vulgar language. This API is called after a user successfully signs in and is taken to their dashboard. The returned list is then used to validate or sanitize user-provided input (for very good reason). Instead of manually implementing and maintaining duplicate logic to filter out these words, with the inclusion of these additional overrides, they could simply pass in their list of restricted terms and proceed with their process. This approach also makes it easier to remove characters that could potentially expose the system to injection vulnerabilities.

For instance, if Ally didn’t want users to manually clean up their passphrases, they could sanitize a phrase like:

"ThisBadWordIsOtherBadWordMyLastBadWordPassword!"

They could then call:

a.Remove(["FirstBadWord", "OtherBadWord", "LastBadWord"])

Or, for case-insensitive matching:

a.Remove(["FirstBadWord", "OtherBadWord", "LastBadWord"], StringComparison.OrdinalIgnoreCase)

The result would be the sanitized password: ThisIsMyPassword!, which they could store securely. Then, during login, they could call the same function to sanitize the entered password (which may contain vulgar or restricted terms), and compare it to the sanitized stored password. This would allow the user to log in with their original password, while the system ensures it matches the sanitized version stored securely, keeping the process transparent to the customer. This approach simplifies string sanitization without duplicating logic. Note: I recognize that this example wouldn’t be a best practice, and I wouldn't recommend or use it myself. It simply serves to demonstrate one way they could utilize these new overrides.

Overall, this would be a valuable quality-of-life improvement and would better align the semantic meanings of "Replace" vs "Remove". Currently, "Replace" is used in cases where the intention is not to replace, but rather to remove content. Clarifying this distinction would make the API more intuitive and consistent with its intended behavior.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Dec 23, 2024
@martincostello
Copy link
Member

Looks like this issue should be transferred to dotnet/runtime as it doesn't appear to be specific to ASP.NET Core.

@Bond-Addict
Copy link
Author

Looks like this issue should be transferred to dotnet/runtime as it doesn't appear to be specific to ASP.NET Core.

That a good point. I'll do that and reference it here. Thanks!

@Bond-Addict
Copy link
Author

Transferred to the dotnet/runtime repo as dotnet/runtime#110912

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically
Projects
None yet
Development

No branches or pull requests

2 participants