是否有不区分大小写的替代string.Replace的字符串?


306

我需要搜索一个字符串,%FirstName%%PolicyAmount%用从数据库中提取的值替换所有出现的和。问题是FirstName的大小写不同。那使我无法使用该String.Replace()方法。我看过有关该主题的网页

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

但是由于某种原因,当我尝试用替换%PolicyAmount%$0,替换从未发生。我认为它与正则表达式中的保留字符美元符号有关。

我是否可以使用另一种方法,而不涉及对输入进行处理以处理正则表达式特殊字符?


1
如果“ $ 0”是传入的变量,则根本不影响正则表达式。
cfeduke

Answers:


132

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

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

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

16
在这种特殊情况下,这很好,但是在从外部输入字符串的情况下,不能确定它们是否不包含表示正则表达式特殊的字符
Allanrbo 2011年

23
您应该这样转义特殊字符:字符串值= Regex.Replace(“%PolicyAmount%”,Regex.Escape(“%PolicyAmount%”),Regex.Escape(“ $ 0”),RegexOptions.IgnoreCase);
Helge Klein

8
在Regex.Replace中使用Regex.Escape时,请当心。您必须转义所有传递的三个字符串,然后对结果调用Regex.Unescape!
Holger Adam

4
根据msdn:“字符转义在正则表达式模式中被识别,而在替换模式中不被识别。” (msdn.microsoft.com/en-us/library/4edbef7e.aspx
布罗尼斯瓦夫

1
最好使用:字符串值= Regex.Replace(“%PolicyAmount%”,Regex.Escape(“%PolicyAmount%”),“ $ 0” .Replace(“ $”,“ $$”),RegexOptions.IgnoreCase); 因为替换仅识别美元符号。
Skorek

295

似乎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();
}

9
真好 我将更ReplaceString改为Replace
AMissico 2010年

41
同意以上评论。可以将其设为具有相同方法名称的扩展方法。只需将其放入带有方法签名的静态类中:公共静态字符串Replace(此String str,string oldValue,string newValue,StringComparison比较)
Mark Robinson

8
通常,@ Helge可能很好,但是我必须从用户那里获取任意字符串,并且不能冒风险输入对正则表达式有意义。当然,我想我可以编写一个循环,并在每个字符前面加一个反斜杠...在这一点上,我也可以做上述事情(恕我直言)。
吉姆(Jim)

9
在进行单元测试时,我遇到了这种情况,当它永远不会返回时oldValue == newValue == ""
Ishmael

10
这是越野车;ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)抛出ArgumentOutOfRangeException
Michael Liu

45

一组令人困惑的答案,部分原因是问题的标题实际上很多比的具体问题,更大的被问。阅读完后,我不确定是否有任何修改要吸收这里所有的好东西,所以我认为我会尝试总结一下。

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

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关于Escape三个方面的评论都是不正确的。初始值和newValue并且不必是。

注意:但是,如果$ s 属于看起来像“捕获的值”标记的一部分,则必须对它们进行转义。因此,Regex.Replace内部的Regex.Replace中的三个美元符号[sic]。没有那个,这样的事情就会打破...

"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之后),以确保您获得了所要的东西适用于重要的用例。让我对Crockford的“ 不安全的正则表达式 ”有所了解。我们经常写正则表达式来允许我们想要的(如果幸运的话),但是无意中允许更多(例如$10,上面的newValue regexp中是否真的有一个有效的“捕获值”字符串?),因为我们不够周到。两种方法都有其价值,并且都可以引发不同类型的意外错误。低估复杂性通常很容易。

这种怪异的$逃避(Regex.Escape并没有逃脱$0我在重置值中所期望的捕获值模式)使我发疯了一段时间。编程难(c)1842


32

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

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;
    }

}

您可能需要处理空字符串/空字符串。
2015年

2
此解决方案中的多个错误:1.检查originalString,oldValue和newValue是否为null。2.不要将orginalString退还(不起作用,简单类型不会通过引用传递),而是先将orginalValue的值分配给新字符串,然后对其进行修改并将其退还。
RWC

31

似乎最简单的方法只是使用.Net附带的且自.Net 1.0开始就存在的Replace方法:

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

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


4
有用。您需要添加对Microsoft.VisualBasic程序集的引用。
CleverPatrick

奇怪的是,当我使用此方法时,它遇到了一些问题(行首的字符丢失了)。最受欢迎的答案来自C. Dragon 76预期的工作。
杰里米·汤普森

1
问题在于,即使不进行替换,它也会返回一个新字符串,其中string.replace()返回一个指向相同字符串的指针。如果您正在执行诸如套用信函合并之类的操作,可能会导致效率低下。
Brain2000

4
Brain2000,你错了。.NET中的所有字符串都是不可变的。
Der_Meister

尽管您说的没错,但Der_Meister并没有使Brain2000所说的错。
西蒙·休伊特

11
    /// <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);
    }

哪个更好的方法?关于stackoverflow.com/a/244933/206730是什么?更好的性能?
Kiquenet

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;
}

+1(在不需要时不使用正则表达式)。当然,您使用了几行代码,但是它比基于正则表达式的替换要有效得多,除非您需要$功能。
ChrisG

6

通过将C.Dragon 76的代码扩展为可重载默认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();
     }
}

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();
}

2

一个类似于C. Dragon's的版本,但是如果您只需要一个替换:

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

1

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

    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();
    }

您能解释一下为什么要用MatchNo相乘吗?
Aheho 2014年

如果oldValue和newValue的长度不同,则替换值时字符串会变长或变短。match.Index指的是字符串中的原始位置,由于我们的替换,我们需要针对该位置的移动进行调整。另一种方法是从右到左执行“删除/插入”。
布兰登2014年

我明白了。这就是“偏移”变量的用途。我不明白的是为什么您要乘以matchNo。我的直觉告诉我,字符串中匹配项的位置与先前发生的实际计数无关。
Aheho 2014年

没关系,我现在明白了。需要根据出现次数对偏移量进行缩放。如果您每次需要替换都丢失2个字符,则在计算remove方法的参数时需要考虑到这一点
Aheho 2014年

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

3
这行不通。$不在令牌中。在带字符串的strReplace中。
Aheho

9
而且您不能适应它吗?
Joel Coehoorn

18
该站点应该是正确答案的存储库。没有几乎正确的答案。
Aheho

0

正则表达式方法应该起作用。但是,您还可以执行以下操作:数据库中的字符串使用小写字母,%variables%使用小写字母,然后在数据库中使用小写字母的字符串中找到位置和长度。请记住,字符串中的位置不会仅仅因为其小写而改变。

然后使用一个反向循环(如果不这样做,它会更容易,如果不这样做,您将不得不不断计数后面的点移动的位置),从数据库中的非小写字符串中删除其位置和位置的%variables%长度并插入替换值。


相反,我的意思是从最远到最短的顺序对找到的位置进行处理,而不是对数据库中的字符串进行反向遍历。
cfeduke

您可以,也可以只使用Regex :)
Ray

0

(因为每个人都为此开枪)。这是我的版本(使用null检查,并正确输入和替换转义)**来自互联网和其他版本的启发:

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");

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();
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.