首页 文章

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

提问于
浏览
296

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

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

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

是否有其他方法可以使用,不涉及清理输入以处理正则表达式特殊字符?

15 回答

  • 36

    From MSDN
    $ 0 - "Substitutes the last substring matched by group number number (decimal)."

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

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

    看起来像 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();
    }
    
  • 30

    有点令人困惑的答案,部分原因是问题的 Headers 实际上比要求的具体问题大得多 . 通读后,我试着总结一下 .

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

    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 不需要 .

    Note: 但是,您必须在插入 if they're part of what would appear to be a "captured value" marker 的新值中转义 $ . 因此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正则表达式中是否真的是一个有效的"capture value"字符串,上面?)因为我们通常很容易被忽略复杂 .

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

  • 10

    这是一种扩展方法 . 不确定我在哪里找到它 .

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

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

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

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

  • 127
    /// <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);
        }
    
  • 0

    受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;
    }
    
  • 8

    扩展C. Dragon 76的流行答案,将他的代码放入一个超载默认 Replace 方法的扩展 .

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

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

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

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

    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();
        }
    
  • 292
    Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
    
  • 0

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

    然后使用一个反向循环(它更容易,如果你不这样做,你将不得不保持后续点移动到的位置的运行计数)通过位置和长度从数据库中删除%lower cased字符串%variables%并插入替换值 .

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

    让我说出我的情况,如果你愿意,你可以把我撕成碎片 .

    相对来说,正则表达式不是这个问题的答案 - 太慢和内存饥饿 .

    StringBuilder比字符串重整更好 .

    由于这将是一个补充 string.Replace 的扩展方法,我认为重要的是匹配它的工作方式 - 因此抛出相同参数问题的异常非常重要,因为如果没有替换则返回原始字符串 .

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

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

    虽然IndexOf将匹配,但源字符串(1)中的匹配长度与oldValue.Length(2)之间存在不匹配 . 当oldValue.Length被添加到当前匹配位置并且我无法找到解决方法时,这表现为在其他一些解决方案中引入IndexOutOfRange . Regex无论如何都无法匹配案例,所以我采用了仅使用 StringComparison.OrdinalIgnoreCase 的实用解决方案来解决问题 .

    我的代码与其他答案类似,但我的转折是我在找到创建 StringBuilder 的麻烦之前寻找匹配 . 如果没有找到,则避免潜在的大分配 . 然后代码成为 do{...}while 而不是 while{...}

    我已经针对其他Answers进行了一些广泛的测试,这种测试速度更快,使用的内存略少 .

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

相关问题