Мне нужно найти строку и заменить все вхождения %FirstName% и %PolicyAmount% значением, полученным из базы данных. Проблема в том, что в FirstName используются разные заглавные буквы. Это мешает мне использовать метод String.Replace(). Я видел веб-страницы по этой теме, которые предлагают
Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);
Однако по какой-то причине, когда я пытаюсь заменить %PolicyAmount% на $0, замена никогда не происходит. Я предполагаю, что это как-то связано с тем, что знак доллара является зарезервированным символом в регулярном выражении.
Есть ли другой метод, который я могу использовать, который не включает дезинфекцию ввода для работы со специальными символами регулярного выражения?





Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
Это не работает. Знак $ отсутствует в токене. Он находится в строке strReplace With.
И вы не можете приспособить его для этого?
Этот сайт должен быть хранилищем правильных ответов. Не почти правильные ответы.
Метод регулярного выражения должен работать. Однако вы также можете сделать нижний регистр строки из базы данных, нижний регистр -% переменных%, которые у вас есть, а затем найти позиции и длину в строке нижнего регистра из базы данных. Помните, что позиции в строке не меняются только потому, что она записана в нижнем регистре.
Затем, используя цикл, который идет в обратном порядке (это проще, если вы этого не сделаете, вам придется вести текущий счетчик того, куда перемещаются более поздние точки), удалите из вашей строки без нижнего регистра из базы данных% переменных% по их положению и length и вставьте значения замены.
Под реверсом я подразумеваю обработку найденных местоположений в обратном порядке от самого дальнего к самому короткому, а не обратный переход по строке из базы данных.
Вы могли бы, или вы могли бы просто использовать Regex :)
Из MSDN
$ 0 - "Заменяет последнюю подстроку, совпадающую с номером группы (десятичным)."
В .NET регулярных выражениях группа 0 всегда является полным совпадением. Для буквального $ вам нужно
string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
в данном конкретном случае это нормально, но в случаях, когда строки вводятся извне, нельзя быть уверенным, что они не содержат символов, которые означают что-то особенное в регулярных выражениях.
Вам следует избегать специальных символов, таких как: строковое значение = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase);
На самом деле экранирование второй строки с помощью регулярного выражения не будет иметь никакого эффекта, кроме получения дополнительного \ перед заменой. Чтобы игнорировать специальные символы в строке замены, вам лучше написать средство сопоставления, которое возвращает саму строку.
Будьте осторожны при использовании Regex.Escape в Regex.Replace. Вам нужно будет экранировать все три переданных строки и вызвать Regex.Unescape для результата!
Согласно msdn: «Экраны символов распознаются в шаблонах регулярных выражений, но не в шаблонах замены». (msdn.microsoft.com/en-us/library/4edbef7e.aspx)
@HolgerAdam хм, я не могу получить ваш комментарий. «Regex.Replace (« a [b] b », Regex.Escape («] B »), Regex.Escape («] C »), RegexOptions.IgnoreCase)» возвращает a [b] C, как и ожидалось. Можете ли вы объяснить, почему, по вашему мнению, нужно избегать ввода и отменять экранирование после?
Лучше всего использовать: строковое значение = Regex.Replace («% PolicyAmount%», Regex.Escape («% PolicyAmount%»), «$ 0» .Replace («$», «$$»), RegexOptions.IgnoreCase); поскольку замена распознает только дуольные знаки.
Похоже, что у 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();
}
Методы расширения работают только в 3+, верно? +1 Все равно, так как OP не был конкретным, но вы можете упомянуть об этом
Кроме того, это будет быстрее, чем регулярное выражение.
Хороший. Я бы сменил ReplaceString на Replace.
Согласитесь с комментариями выше. Это может быть преобразовано в метод расширения с тем же именем. Просто вставьте его в статический класс с сигнатурой метода: публичная статическая строка Replace (эта строка str, строка oldValue, строка newValue, сравнение StringComparison)
Скорость - это еще не все. Используйте регулярное выражение вместо того, чтобы делать это самостоятельно, что внесет дополнительную сложность и, возможно, также ошибки. Кроме того, решение с регулярным выражением намного легче читать и поддерживать.
@Helge, в общем, это может быть хорошо, но я должен принимать произвольные строки от пользователя и не могу рисковать, что ввод будет значимым для регулярного выражения. Конечно, я думаю, я мог бы написать цикл и поставить обратную косую черту перед каждым символом ... В этот момент я мог бы сделать то же самое (ИМХО).
@Jim - Я согласен использовать это решение, но на всякий случай, если оно вам когда-нибудь понадобится, вы можете использовать Regex.Escape для экранирования важных для регулярных выражений символов.
@JamesManning - Хм, интересно - не знал о Escape (). Спасибо.
Во время модульного тестирования я столкнулся с тем, что он никогда не вернется, когда oldValue == newValue == "".
В случае oldValue = "" String.Replace этого не допускает. Я добавил проверки исключений для соответствия String.Replace exceptions: if (oldValue == null) { throw new ArgumentNullException("oldValue"); } if (oldValue == "") { throw new ArgumentException("String cannot be of zero length.", "oldValue"); }
Здесь отличная работа. Я превратил его в метод расширения, но, что более важно, я добавил ускорение вверху в случае, когда str не содержит oldValue. Просто переместите int index = str.IndexOf(oldValue, comparison); в первую строку метода и верните str, если index == -1
Это глючит; ReplaceString("œ", "oe", "", StringComparison.InvariantCulture) выкидывает ArgumentOutOfRangeException.
Просто изучите Regex, держите код в чистоте. Это тривиальный пример, но все же выглядит сложным. К сожалению, людям, одержимым скоростью, а затем пишущим такой убогий код, не повезло.
@Jaycee, необходимость экранирования строки замены по умолчанию не выглядит для меня чистым кодом. Также я уверен, что реальная реализация Regex выглядит намного сложнее и, вероятно, имела множество ошибок в своих начальных версиях. Я очень надеюсь, что будет опубликована последняя версия без ошибок.
@crokusek Я широко использую регулярные выражения и замеченных мною ошибок не обнаружил. У вас гораздо больше шансов ввести ошибку с таким пользовательским кодом.
Использование StringBuilder таким образом, скорее всего, не улучшит производительность так, как вы предполагали; он будет инициализирован 16-символьным буфером, и ваш цикл потенциально вызовет ряд выделений памяти и копий. Вы должны инициализировать ваш StringBuilder до подходящей емкости, прежде чем вы начнете добавлять к нему строки.
@MichaelLiu Что ты думаешь о if (oldValue.Length > str.Length) return str; ... Это могло вызвать какие-то странные вещи. Я написал несколько тестов, все используют OrdinalIgnoreCase, и этот обходной путь ни в одном из них не сломался. Конечно, я могу упустить некоторые случаи, так что вы думаете?
@MichaelLiu Вот тесты для этого Замени gist.github.com/Galilyou/00dcd0dab2d2a050c30c
@Galilyou: Проблема, на которую я указал, не связана с проверкой длины; проблема связана с IndexOf и StringComparison.InvariantCulture.
примечание: ReplaceString("","","",StringComparison.CurrentCulture) приведет к бесконечному циклу!
@WaldenLeverich Значит, вы избегаете создания экземпляра StringBuilder, но вводите оператор if? Мне кажется, это микрооптимизация (если что).
@Caltor Это не только конструктор, но и копия строки после while (), а затем .ToString обратно в строку. Эти вещи складываются. Но что еще более важно, разработчик может быстро увидеть, что произойдет, если совпадения нет. Кстати, проверьте код MS на наличие аналогичных проверок, а также проверки быстрого выхода.
Кажется, самый простой способ - просто использовать метод Replace, который поставляется с .Net и существует с .Net 1.0:
string res = Microsoft.VisualBasic.Strings.Replace(res,
"%PolicyAmount%",
"$0",
Compare: Microsoft.VisualBasic.CompareMethod.Text);
Чтобы использовать этот метод, вы должны добавить ссылку на сборку Microsoft.VisualBasic. Эта сборка является стандартной частью среды выполнения .Net, она не является дополнительной загрузкой и не помечена как устаревшая.
Оно работает. Вам нужно добавить ссылку на сборку Microsoft.VisualBasic.
Странно, что у этого метода были проблемы, когда я его использовал (пропали символы в начале строки). Самый популярный ответ здесь от C. Dragon 76 работал, как ожидалось.
Проблема в том, что он возвращает НОВУЮ строку, даже если замена не производится, где string.replace () возвращает указатель на ту же строку. Может стать неэффективным, если вы делаете что-то вроде слияния писем.
Brain2000, вы ошибаетесь. Все строки в .NET неизменяемы.
Der_Meister, хотя то, что вы говорите, правильно, это не означает, что Brain2000 сказал неправильно.
версия, аналогичная версии 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 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;
}
}
Возможно, вам придется обрабатывать пустые / пустые строковые случаи.
Несколько ошибок в этом решении: 1. Проверьте исходные строки, oldValue и newValue на null. 2. Не возвращайте orginalString (не работает, простые типы не передаются по ссылке), а сначала присвойте значение orginalValue новой строке, измените ее и верните.
/// <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? лучшая производительность?
Типа запутанная группа ответов, отчасти потому, что заголовок вопроса на самом деле много больше, чем конкретный задаваемый вопрос. Прочитав, я не уверен, что какой-либо ответ - это несколько правок от усвоения всего хорошего здесь, поэтому я решил, что попытаюсь подвести итог.
Вот метод расширения, который, как мне кажется, позволяет избежать упомянутых здесь ошибок и обеспечивает наиболее широко применимое решение.
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);
}
Так...
"œ".ReplaceCaseInsensitiveFind("oe", ""), хотя, возможно, он имел в виду немного другое поведение.К сожалению, Комментарий @HA о том, что у вас есть Escape, все три неверны. Начальное значение и newValue не обязательно.
Примечание: Однако вам нужно экранировать $ в новом значении, которое вы вставляете если они являются частью маркера "захваченного значения". Таким образом, три знака доллара в 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, чувствуют, что их использование позволяет избежать ошибок, но я часто все еще неравнодушен к строкам с байтовым сниффингом (но только после прочтения Спольский по кодировкам), чтобы быть абсолютно уверенным, что вы получаете то, для чего рассчитывали. важные варианты использования. Немного напоминает мне Крокфорда из "небезопасные регулярные выражения". Слишком часто мы пишем регулярные выражения, которые позволяют то, что мы хотим (если нам повезет), но непреднамеренно допускаем больше (например, действительно ли $10 является допустимой строкой «значения захвата» в моем регулярном выражении newValue, выше?), Потому что мы не были внимательны. достаточно. Оба метода имеют ценность, и оба поощряют разные типы непреднамеренных ошибок. Сложность часто легко недооценить.
Это странное экранирование $ (и то, что Regex.Escape не избегает шаблонов захваченных значений, таких как $0, как я ожидал в значениях замены) на какое-то время свело меня с ума. Программирование - это сложно (с) 1842
Вот еще один вариант выполнения замен 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?
Если есть разница в длине между oldValue и newValue, строка будет становиться длиннее или короче по мере замены значений. match.Index относится к исходному местоположению в строке, нам нужно скорректировать перемещение этих позиций из-за нашей замены. Другой подход - выполнить Remove / Insert справа налево.
Я понимаю. Для этого и нужна переменная "смещение". Я не понимаю, почему вы умножаете на matchNo. Моя интуиция подсказывает мне, что расположение совпадения в строке не имеет никакого отношения к фактическому количеству предыдущих вхождений.
Неважно, теперь я понял. Смещение необходимо масштабировать в зависимости от количества вхождений. Если вы теряете 2 символа каждый раз, когда вам нужно выполнить замену, вам необходимо учитывать это при вычислении параметров для метода удаления.
Вдохновленный ответом 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;
}
+1 за то, что не использует регулярное выражение, когда это не нужно. Конечно, вы используете еще несколько строк кода, но это намного эффективнее, чем замена на основе регулярных выражений, если вам не нужна функциональность $.
На основе ответа Джеффа Редди с некоторыми оптимизациями и проверками:
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();
}
Расширение популярного ответа С. Дракон 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();
}
}
(Поскольку все делают ставку). Вот моя версия (с нулевыми проверками и правильным вводом и экранированием замены) ** Вдохновлена интернетом и другими версиями:
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). Это проявилось в том, что в некоторых других решениях вызывается IndexOutOfRange, когда oldValue.Length был добавлен к текущей позиции совпадения, и я не мог найти способ обойти это.
Regex в любом случае не соответствует случаю, поэтому я принял прагматичное решение - использовать только 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();
}
Начиная с .NET Core 2.0 или .NET Standard 2.1 соответственно, это встроено в среду выполнения .NET [1]:
"hello world".Replace("World", "csharp", StringComparison.CurrentCultureIgnoreCase); // "hello csharp"
Если входящая переменная «$ 0» не влияет на регулярное выражение.