我需要搜索一个字符串并将所有出现的 %FirstName%
和 %PolicyAmount%
替换为从数据库中提取的值。问题是 FirstName 的大小写不同。这使我无法使用 String.Replace()
方法。我看过有关该主题的网页建议
Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);
但是由于某种原因,当我尝试用 $0
替换 %PolicyAmount%
时,替换永远不会发生。我认为这与美元符号是正则表达式中的保留字符有关。
我可以使用另一种不涉及清理输入以处理正则表达式特殊字符的方法吗?
StringComparison.OrdinalIgnoreCase
作为第三个参数结合在一起。
似乎 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();
}
From MSDN
$0 - “替换与组编号匹配的最后一个子字符串(十进制)。”
在 .NET 正则表达式中,组 0 始终是整个匹配项。对于文字 $ 你需要
string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
一组令人困惑的答案,部分原因是问题的标题实际上比所问的具体问题大得多。通读后,我不确定任何答案是否与吸收这里所有的好东西相距甚远,所以我想我会尝试总结一下。
这是一种扩展方法,我认为它避免了这里提到的陷阱,并提供了最广泛适用的解决方案。
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);
}
所以...
这是一个扩展方法@MarkRobinson
这不会尝试跳过 Regex @Helge(如果您想在 Regex 之外进行这样的字符串嗅探,您真的必须逐字节进行)
通过了@MichaelLiu 的出色测试用例 "œ".ReplaceCaseInsensitiveFind("oe", ""),尽管他的想法可能略有不同。
不幸的是,@HA 's comment that you have to Escape
all three isn't correct。初始值和 newValue
不需要。
注意:但是,您必须在插入的新值中转义 $
,如果它们是看似“捕获的值”标记的一部分。因此,Regex.Replace [sic] 中的 Regex.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 之后)才能绝对确保你得到你想要的用于重要的用例。让我想起了“insecure regular expressions”上的 Crockford。我们经常编写允许我们想要的正则表达式(如果我们幸运的话),但无意中允许更多(例如,在我的 newValue 正则表达式中,$10
真的是一个有效的“捕获值”字符串吗?)因为我们不是t 够周到。这两种方法都有价值,并且都鼓励不同类型的无意错误。通常很容易低估复杂性。
那种奇怪的 $
转义(并且那个 Regex.Escape
并没有像我在替换值中所期望的那样逃脱像 $0
这样的捕获值模式)让我发疯了一段时间。编程很难(c)1842
这是一个扩展方法。不知道我在哪里找到的。
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;
}
}
似乎最简单的方法是简单地使用 .Net 附带的 Replace 方法,并且自 .Net 1.0 以来一直存在:
string res = Microsoft.VisualBasic.Strings.Replace(res,
"%PolicyAmount%",
"$0",
Compare: Microsoft.VisualBasic.CompareMethod.Text);
为了使用此方法,您必须添加对 Microsoft.VisualBasic 程序集的引用。此程序集是 .Net 运行时的标准部分,它不是额外下载或标记为过时的。
C. Dragon 76
中最受欢迎的答案按预期工作。
/// <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);
}
受 cfeduke 的回答启发,我制作了这个函数,它使用 IndexOf 来查找字符串中的旧值,然后用新值替换它。我在处理数百万行的 SSIS 脚本中使用了它,而正则表达式方法比这慢得多。
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;
}
通过将他的代码变成重载默认 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();
}
}
基于 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();
}
由于分别从 .NET Core 2.0 或 .NET Standard 2.1 开始,这已被纳入 .NET 运行时 [1]:
"hello world".Replace("World", "csharp", StringComparison.CurrentCultureIgnoreCase); // "hello csharp"
类似于 C. Dragon 的版本,但如果您只需要一个替换:
int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase);
if (n >= 0)
{
myText = myText.Substring(0, n)
+ newValue
+ myText.Substring(n + oldValue.Length);
}
这是执行正则表达式替换的另一个选项,因为似乎没有多少人注意到匹配包含字符串中的位置:
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();
}
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
正则表达式方法应该可以工作。但是,您还可以将数据库中的字符串小写,将您拥有的 %variables% 小写,然后从数据库中找到小写字符串中的位置和长度。请记住,字符串中的位置不会仅仅因为它的小写而改变。
然后使用一个反向循环(它更容易,如果你不这样做,你将不得不保持对以后点移动到的位置的运行计数)从数据库中的非小写字符串中删除 %variables% 通过它们的位置和长度并插入替换值。
(因为每个人都在尝试这个)。这是我的版本(带有空检查,以及正确的输入和替换转义)** 灵感来自互联网和其他版本:
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");
让我证明我的情况,然后如果你愿意,你可以把我撕成碎片。
正则表达式不是这个问题的答案 - 相对来说太慢而且内存太小。
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();
}
不定期副业成功案例分享
ReplaceString
更改为Replace
。oldValue == newValue == ""
时它永远不会返回的情况。ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)
抛出ArgumentOutOfRangeException
。