Есть ли альтернатива string.Заменить это без учета регистра?

мне нужно найти строку и заменить все вхождения %FirstName% и %PolicyAmount% со значением из базы данных. Проблема капитализации имя меняется. Это мешает мне использовать String.Replace() метод. Я видел веб-страницы на эту тему, которые предлагают

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

однако по какой-то причине, когда я пытаюсь заменить %PolicyAmount% С замена не происходит. Я предполагаю, что это связано с тем, что знак доллара является зарезервированным символом в регулярное выражение.

есть ли другой метод, который я могу использовать, который не включает в себя дезинфекцию ввода для работы со специальными символами regex?

15 ответов


из MSDN
$0 - " заменяет последнюю подстроку, соответствующую номеру группы (десятичный)."

в .NET регулярных выражениях группа 0 всегда полностью совпадает. Для буквального $ вам нужно

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

кажется string.Replace должны есть перегрузка, которая принимает


вид запутанной группы ответов, отчасти потому, что название вопроса на самом деле много больше, чем конкретный заданный вопрос. После прочтения я не уверен, что любой ответ-это несколько изменений от ассимиляции всех хороших вещей здесь, поэтому я решил попытаться подвести итог.

вот метод расширения, который, я думаю, избегает подводных камней, упомянутых здесь, и обеспечивает наиболее широкое применение решение.

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

так...

к сожалению, @HA 's комментарий, что вы должны Escape все три не правильно. Начальное значение и newValue не нужно.

Примечание: вы, однако, должны бежать $s в новом значении, которое вы вставляете если они являются частью того, что кажется маркером "захваченного значения". Таким образом, три знака доллара в регулярном выражении.Заменить внутри регулярного выражения.Заменить [sic]. Без этого, что-то вроде этого разрыв...

"This is HIS fork, hIs spoon, hissssssss knife.".ReplaceCaseInsensitiveFind("his", @"her")

вот ошибка:

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, чувствуют, что их использование позволяет избежать ошибок, но я часто все еще неравнодушен к байтовым строкам обнюхивания (но только после чтения Спольски о кодировках), чтобы быть абсолютно уверенным, что вы получаете то, что вы предназначены для важных случаев использования. Напоминает мне Крокфорда в"небезопасные регулярные выражения" немного. Слишком часто мы напишите регулярные выражения, которые позволяют то, что мы хотим (если нам повезет), но непреднамеренно позволяют больше (например, is действительно допустимая строка "значение захвата" в моем регулярном выражении newValue выше? потому что мы были недостаточно внимательны. Оба метода имеют ценность, и оба поощряют различные типы непреднамеренных ошибок. Часто легко недооценивать сложность.

странно $ побег (а это Regex.Escape не бежать в плен моделей значение как как я и ожидал в значения замены) сводили меня с ума на некоторое время. Программирование трудно (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;
    }

}

Кажется, самый простой метод-просто использовать метод Replace, который поставляется с .Net и существует с .Net 1.0:

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

для использования этого метода необходимо добавить ссылку на Microsoft.VisualBasic assemblly. Эта сборка является стандартной частью среды выполнения .Net, она не является дополнительной загрузкой или помечена как устаревшая.


    /// <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, обрабатывающем миллионы строк, и 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;
}

расширения С. Дракон 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();
     }
}

на основе ответа Джеффа Редди, с некоторыми оптимизациями и проверками:

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

версия, похожая на 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);

метод регулярного выражения должен работать. Однако то, что вы также можете сделать, - это строчная строка из базы данных, строчная строка %переменных%, а затем найдите позиции и длины в нижней регистровой строке из базы данных. Помните, что позиции в строке не меняются только потому, что ее нижняя оболочка.

затем с помощью цикла, который идет в обратном направлении (его проще, если вы этого не сделаете, вам придется держать подсчет, куда позже перемещаются точки) удалить из вашего не-нижняя строка cased из базы данных % переменные% по их положению и длине и вставить значения замены.


(Так как все делают выстрел в этом). Вот моя версия (с нулевыми проверками и корректным сбеганием ввода и замены) * * вдохновленный со всего интернета и других версий:

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

позвольте мне сделать мое дело, а затем вы можете разорвать меня в клочья, если хотите.

Regex не является ответом на эту проблему-слишком медленно и голодная память, относительно говоря.

StringBuilder намного лучше, чем строка искажения.

так как это будет метод расширения для дополнения string.Replace, Я считаю, что важно соответствовать тому, как это работает - поэтому выбрасывание исключений для тех же проблем аргумента важно, что и возврат исходной строки, если замена не производилась.

Я считаю, что наличие параметра StringComparison не является хорошей идеей. Я попробовал, но тестовый случай, первоначально упомянутый Майклом-Лю, показал проблему: -

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

в то время как IndexOf будет соответствовать, существует несоответствие между длиной соответствия в исходной строке (1) и oldValue.Длина (2). Это проявилось, вызвав IndexOutOfRange в некоторых других решениях, когда oldValue.Длина была добавлена к текущей позиции матча и I не мог найти способ обойти это. 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();
    }