C# - Simplest way to remove first occurrence of a substring from another string

我需要从另一个字符串中删除一个字符串的第一个匹配项(并且只删除第一个匹配项)。

下面是一个替换字符串 "\\Iteration"的例子:

ProjectName\\Iteration\\Release1\\Iteration1

会变成这样:

ProjectName\\Release1\\Iteration1

下面是一些代码:

const string removeString = "\\Iteration";
int index = sourceString.IndexOf(removeString);
int length = removeString.Length;
String startOfString = sourceString.Substring(0, index);
String endOfString = sourceString.Substring(index + length);
String cleanPath = startOfString + endOfString;

好像有很多代码。

所以我的问题是: 有没有一种更清晰、更易读、更简洁的方法来做到这一点?

183459 次浏览
string myString = sourceString.Remove(sourceString.IndexOf(removeString),removeString.Length);

EDIT: @OregonGhost is right. I myself would break the script up with conditionals to check for such an occurence, but I was operating under the assumption that the strings were given to belong to each other by some requirement. It is possible that business-required exception handling rules are expected to catch this possibility. I myself would use a couple of extra lines to perform conditional checks and also to make it a little more readable for junior developers who may not take the time to read it thoroughly enough.

int index = sourceString.IndexOf(removeString);
string cleanPath = (index < 0)
? sourceString
: sourceString.Remove(index, removeString.Length);

Wrote a quick TDD Test for this

    [TestMethod]
public void Test()
{
var input = @"ProjectName\Iteration\Release1\Iteration1";
var pattern = @"\\Iteration";


var rgx = new Regex(pattern);
var result = rgx.Replace(input, "", 1);


Assert.IsTrue(result.Equals(@"ProjectName\Release1\Iteration1"));
}

rgx.Replace(input, "", 1); says to look in input for anything matching the pattern, with "", 1 time.

You could use an extension method for fun. Typically I don't recommend attaching extension methods to such a general purpose class like string, but like I said this is fun. I borrowed @Luke's answer since there is no point in re-inventing the wheel.

[Test]
public void Should_remove_first_occurrance_of_string() {


var source = "ProjectName\\Iteration\\Release1\\Iteration1";


Assert.That(
source.RemoveFirst("\\Iteration"),
Is.EqualTo("ProjectName\\Release1\\Iteration1"));
}


public static class StringExtensions {
public static string RemoveFirst(this string source, string remove) {
int index = source.IndexOf(remove);
return (index < 0)
? source
: source.Remove(index, remove.Length);
}
}

I definitely agree that this is perfect for an extension method, but I think it can be improved a bit.

public static string Remove(this string source, string remove,  int firstN)
{
if(firstN <= 0 || string.IsNullOrEmpty(source) || string.IsNullOrEmpty(remove))
{
return source;
}
int index = source.IndexOf(remove);
return index < 0 ? source : source.Remove(index, remove.Length).Remove(remove, --firstN);
}

This does a bit of recursion which is always fun.

Here is a simple unit test as well:

   [TestMethod()]
public void RemoveTwiceTest()
{
string source = "look up look up look it up";
string remove = "look";
int firstN = 2;
string expected = " up  up look it up";
string actual;
actual = source.Remove(remove, firstN);
Assert.AreEqual(expected, actual);


}
sourceString.Replace(removeString, "");

If you'd like a simple method to resolve this problem. (Can be used as an extension)

See below:

    public static string RemoveFirstInstanceOfString(this string value, string removeString)
{
int index = value.IndexOf(removeString, StringComparison.Ordinal);
return index < 0 ? value : value.Remove(index, removeString.Length);
}

Usage:

    string valueWithPipes = "| 1 | 2 | 3";
string valueWithoutFirstpipe = valueWithPipes.RemoveFirstInstanceOfString("|");
//Output, valueWithoutFirstpipe = " 1 | 2 | 3";

Inspired by and modified @LukeH's and @Mike's answer.

Don't forget the StringComparison.Ordinal to prevent issues with Culture settings. https://www.jetbrains.com/help/resharper/2018.2/StringIndexOfIsCultureSpecific.1.html