是否有一个不区分大小写的string.Replace的替代方法?

时间:2008-10-28 19:31:17

标签: c# .net string .net-2.0 replace

我需要搜索一个字符串,并将所有出现的%FirstName%%PolicyAmount%替换为从数据库中提取的值。问题是FirstName的大小写有所不同。这阻止我使用String.Replace()方法。我已经看过关于该主题的网页建议

Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);

但是,出于某种原因,当我尝试将%PolicyAmount%替换为$0时,替换永远不会发生。我认为它与美元符号是正则表达式中的保留字符有关。

我可以使用另一种方法,不涉及清理处理正则表达式特殊字符的输入吗?

16 个答案:

答案 0 :(得分:293)

似乎string.Replace 应该有一个带有StringComparison参数的重载。既然没有,你可以尝试这样的事情:

public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison)
{
    StringBuilder sb = new StringBuilder();

    int previousIndex = 0;
    int index = str.IndexOf(oldValue, comparison);
    while (index != -1)
    {
        sb.Append(str.Substring(previousIndex, index - previousIndex));
        sb.Append(newValue);
        index += oldValue.Length;

        previousIndex = index;
        index = str.IndexOf(oldValue, index, comparison);
    }
    sb.Append(str.Substring(previousIndex));

    return sb.ToString();
}

答案 1 :(得分:127)

From MSDN
$ 0 - “替换与组号(十进制)匹配的最后一个子串。”

在.NET正则表达式中,组0始终是整个匹配。对于文字$,你需要

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);

答案 2 :(得分:41)

有点令人困惑的答案,部分原因是问题的标题实际上大于要求的具体问题。在阅读完之后,我不确定任何答案是否能够吸收所有好东西的一些编辑,所以我想我会尝试总结。

我认为这是一种扩展方法,可以避免此处提到的陷阱,并提供最广泛适用的解决方案。

public static string ReplaceCaseInsensitiveFind(this string str, string findMe,
    string newValue)
{
    return Regex.Replace(str,
        Regex.Escape(findMe),
        Regex.Replace(newValue, "\\$[0-9]+", @"$$$0"),
        RegexOptions.IgnoreCase);
}

因此...

不幸的是,@HA 's comment that you have to Escape all three isn't correct。初始值和newValue不需要。

注意:但是,您必须在新值中转义$,如果他们是的一部分似乎是一个被捕获的价值"标记即可。因此Regex.Replace里面的三个美元符号.Replace里面的内容。[原文如此]。没有它,这样的事情会破坏......

"This is HIS fork, hIs spoon, hissssssss knife.".ReplaceCaseInsensitiveFind("his", @"he$0r")

这是错误:

An unhandled exception of type 'System.ArgumentException' occurred in System.dll

Additional information: parsing "The\hisr\ is\ he\HISr\ fork,\ he\hIsr\ spoon,\ he\hisrsssssss\ knife\." - Unrecognized escape sequence \h.

告诉你什么,我知道那些对Regex感到满意的人觉得他们的使用避免了错误,但是我经常仍然偏向于字节嗅探字符串(但只有在阅读Spolsky on encodings之后才绝对)确保你获得了重要用例的预期。让我想起了Crockford" insecure regular expressions"一点。我们经常编写允许我们想要的正则表达式(如果我们很幸运),但无意中允许更多(例如,$10真的是有效的"捕获值"在我的newValue中的字符串regexp,上面?)因为我们不够周到。这两种方法都有价值,并且都鼓励不同类型的无意识错误。通常很容易低估复杂性。

奇怪的$逃避(Regex.Escape并没有像$0那样逃避被捕获的价值模式,正如我在预期的替换值中所做的那样)让我疯了一会儿。编程很难(c)1842

答案 3 :(得分:30)

似乎最简单的方法就是使用.Net附带的Replace方法,并且自.Net 1.0以来一直存在:

string res = Microsoft.VisualBasic.Strings.Replace(res, 
                                   "%PolicyAmount%", 
                                   "$0", 
                                   Compare: Microsoft.VisualBasic.CompareMethod.Text);

为了使用此方法,您必须添加对Microsoft.VisualBasic组件的引用。此程序集是.Net运行时的标准部分,它不是额外的下载或标记为过时。

答案 4 :(得分:30)

这是一种扩展方法。不知道我在哪里找到它。

public static class StringExtensions
{
    public static string Replace(this string originalString, string oldValue, string newValue, StringComparison comparisonType)
    {
        int startIndex = 0;
        while (true)
        {
            startIndex = originalString.IndexOf(oldValue, startIndex, comparisonType);
            if (startIndex == -1)
                break;

            originalString = originalString.Substring(0, startIndex) + newValue + originalString.Substring(startIndex + oldValue.Length);

            startIndex += newValue.Length;
        }

        return originalString;
    }

}

答案 5 :(得分:10)

    /// <summary>
    /// A case insenstive replace function.
    /// </summary>
    /// <param name="originalString">The string to examine.(HayStack)</param>
    /// <param name="oldValue">The value to replace.(Needle)</param>
    /// <param name="newValue">The new value to be inserted</param>
    /// <returns>A string</returns>
    public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
    {
        Regex regEx = new Regex(oldValue,
           RegexOptions.IgnoreCase | RegexOptions.Multiline);
        return regEx.Replace(originalString, newValue);
    }

答案 6 :(得分:8)

受cfeduke的回答启发,我创建了这个函数,它使用IndexOf在字符串中查找旧值,然后用新值替换它。我在处理数百万行的SSIS脚本中使用了这个,而regex方法比这慢。

public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
{
    int prevPos = 0;
    string retval = str;
    // find the first occurence of oldValue
    int pos = retval.IndexOf(oldValue, StringComparison.InvariantCultureIgnoreCase);

    while (pos > -1)
    {
        // remove oldValue from the string
        retval = retval.Remove(pos, oldValue.Length);

        // insert newValue in it's place
        retval = retval.Insert(pos, newValue);

        // check if oldValue is found further down
        prevPos = pos + newValue.Length;
        pos = retval.IndexOf(oldValue, prevPos, StringComparison.InvariantCultureIgnoreCase);
    }

    return retval;
}

答案 7 :(得分:6)

通过将代码转换为重载默认Replace方法的扩展程序来扩展C. Dragon 76的热门答案。

public static class StringExtensions
{
    public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
    {
        StringBuilder sb = new StringBuilder();

        int previousIndex = 0;
        int index = str.IndexOf(oldValue, comparison);
        while (index != -1)
        {
            sb.Append(str.Substring(previousIndex, index - previousIndex));
            sb.Append(newValue);
            index += oldValue.Length;

            previousIndex = index;
            index = str.IndexOf(oldValue, index, comparison);
        }
        sb.Append(str.Substring(previousIndex));
        return sb.ToString();
     }
}

答案 8 :(得分:3)

基于Jeff Reddy的回答,并进行了一些优化和验证:

public static string Replace(string str, string oldValue, string newValue, StringComparison comparison)
{
    if (oldValue == null)
        throw new ArgumentNullException("oldValue");
    if (oldValue.Length == 0)
        throw new ArgumentException("String cannot be of zero length.", "oldValue");

    StringBuilder sb = null;

    int startIndex = 0;
    int foundIndex = str.IndexOf(oldValue, comparison);
    while (foundIndex != -1)
    {
        if (sb == null)
            sb = new StringBuilder(str.Length + (newValue != null ? Math.Max(0, 5 * (newValue.Length - oldValue.Length)) : 0));
        sb.Append(str, startIndex, foundIndex - startIndex);
        sb.Append(newValue);

        startIndex = foundIndex + oldValue.Length;
        foundIndex = str.IndexOf(oldValue, startIndex, comparison);
    }

    if (startIndex == 0)
        return str;
    sb.Append(str, startIndex, str.Length - startIndex);
    return sb.ToString();
}

答案 9 :(得分:2)

类似于C. Dragon的版本,但是如果你只需要一个替代品:

int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase);
if (n >= 0)
{
    myText = myText.Substring(0, n)
        + newValue
        + myText.Substring(n + oldValue.Length);
}

答案 10 :(得分:1)

这是执行正则表达式替换的另一个选项,因为似乎没有多少人注意到匹配包含字符串中的位置:

    public static string ReplaceCaseInsensative( this string s, string oldValue, string newValue ) {
        var sb = new StringBuilder(s);
        int offset = oldValue.Length - newValue.Length;
        int matchNo = 0;
        foreach (Match match in Regex.Matches(s, Regex.Escape(oldValue), RegexOptions.IgnoreCase))
        {
            sb.Remove(match.Index - (offset * matchNo), match.Length).Insert(match.Index - (offset * matchNo), newValue);
            matchNo++;
        }
        return sb.ToString();
    }

答案 11 :(得分:0)

Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);

答案 12 :(得分:0)

正则表达式方法应该有效。然而,您还可以做的是小写数据库中的字符串,小写%变量%,然后从数据库中找到下部字符串中的位置和长度。请记住,字符串中的位置不会因为它的下限而改变。

然后使用反向循环(更容易,如果不这样做,则必须保持后续点移动到的位置的运行计数)从数据库中删除非下限的字符串%变量%by他们的位置和长度,并插入替换值。

答案 13 :(得分:0)

(因为每个人都在为此拍摄)。这是我的版本(使用空检查,正确的输入和替换转义)**灵感来自互联网和其他版本:

using System;
using System.Text.RegularExpressions;

public static class MyExtensions {
    public static string ReplaceIgnoreCase(this string search, string find, string replace) {
        return Regex.Replace(search ?? "", Regex.Escape(find ?? ""), (replace ?? "").Replace("$", "$$"), RegexOptions.IgnoreCase);          
    }
}

用法:

var result = "This is a test".ReplaceIgnoreCase("IS", "was");

答案 14 :(得分:0)

让我陈述我的意思,然后,如果您愿意,您可以将我撕成碎片。

正则表达式不能解决这个问题-相对而言,速度太慢且内存不足。

StringBuilder比字符串整形好得多。

由于这将是扩展string.Replace的扩展方法,因此我认为匹配它的工作方式非常重要-因此,对相同参数问题抛出异常与在未进行替换的情况下返回原始字符串一样重要。

我认为拥有StringComparison参数不是一个好主意。 我确实尝试过,但是michael-liu最初提到的测试用例显示了一个问题:-

[TestCase("œ", "oe", "", StringComparison.InvariantCultureIgnoreCase, Result = "")]

虽然IndexOf将匹配,但是源字符串(1)中的匹配长度与oldValue.Length(2)之间存在不匹配。这是通过在将oldValue.Length添加到当前比赛位置时在其他一些解决方案中导致IndexOutOfRange来体现的,我找不到解决办法。 正则表达式仍然无法匹配大小写,因此我采取了务实的解决方案,仅对解决方案使用StringComparison.OrdinalIgnoreCase

我的代码与其他答案相似,但我的不足之处是,在创建StringBuilder之前,我先寻找匹配项。如果未找到,则避免可能的大分配。然后,代码变为do{...}while而不是while{...}

我对其他答案进行了广泛的测试,结果显示速度更快,并且使用的内存更少。

    public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
    {
        if (str == null) throw new ArgumentNullException(nameof(str));
        if (oldValue == null) throw new ArgumentNullException(nameof(oldValue));
        if (oldValue.Length == 0) throw new ArgumentException("String cannot be of zero length.", nameof(oldValue));

        var position = str.IndexOf(oldValue, 0, StringComparison.OrdinalIgnoreCase);
        if (position == -1) return str;

        var sb = new StringBuilder(str.Length);

        var lastPosition = 0;

        do
        {
            sb.Append(str, lastPosition, position - lastPosition);

            sb.Append(newValue);

        } while ((position = str.IndexOf(oldValue, lastPosition = position + oldValue.Length, StringComparison.OrdinalIgnoreCase)) != -1);

        sb.Append(str, lastPosition, str.Length - lastPosition);

        return sb.ToString();
    }

答案 15 :(得分:0)

由于分别是.NET Core 2.0或.NET Standard 2.1,因此已将其烘焙到.NET运行时[1]中:

"hello world".Replace("World", "csharp", StringComparison.CurrentCultureIgnoreCase); // "hello csharp"

[1] https://docs.microsoft.com/en-us/dotnet/api/system.string.replace#System_String_Replace_System_String_System_String_System_StringComparison _