Есть ли альтернатива string.Replace без учета регистра?

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

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

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

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

Если входящая переменная «$ 0» не влияет на регулярное выражение.

cfeduke 28.10.2008 22:33
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
311
1
124 056
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

Это не работает. Знак $ отсутствует в токене. Он находится в строке strReplace With.

Aheho 28.10.2008 22:58

И вы не можете приспособить его для этого?

Joel Coehoorn 28.10.2008 23:04

Этот сайт должен быть хранилищем правильных ответов. Не почти правильные ответы.

Aheho 29.10.2008 00:00

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

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

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

cfeduke 28.10.2008 22:38

Вы могли бы, или вы могли бы просто использовать Regex :)

Ray 28.10.2008 22:48
Ответ принят как подходящий

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

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

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

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

Allanrbo 07.01.2011 18:21

Вам следует избегать специальных символов, таких как: строковое значение = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase);

Helge Klein 28.02.2011 17:04

На самом деле экранирование второй строки с помощью регулярного выражения не будет иметь никакого эффекта, кроме получения дополнительного \ перед заменой. Чтобы игнорировать специальные символы в строке замены, вам лучше написать средство сопоставления, которое возвращает саму строку.

Paolo Tedesco 14.03.2011 13:15

Будьте осторожны при использовании Regex.Escape в Regex.Replace. Вам нужно будет экранировать все три переданных строки и вызвать Regex.Unescape для результата!

Holger Adam 11.12.2012 12:47

Согласно msdn: «Экраны символов распознаются в шаблонах регулярных выражений, но не в шаблонах замены». (msdn.microsoft.com/en-us/library/4edbef7e.aspx)

Bronek 16.12.2015 15:28

@HolgerAdam хм, я не могу получить ваш комментарий. «Regex.Replace (« a [b] b », Regex.Escape («] B »), Regex.Escape («] C »), RegexOptions.IgnoreCase)» возвращает a [b] C, как и ожидалось. Можете ли вы объяснить, почему, по вашему мнению, нужно избегать ввода и отменять экранирование после?

swe 16.03.2017 15:03

Лучше всего использовать: строковое значение = Regex.Replace («% PolicyAmount%», Regex.Escape («% PolicyAmount%»), «$ 0» .Replace («$», «$$»), RegexOptions.IgnoreCase); поскольку замена распознает только дуольные знаки.

Skorek 14.06.2017 15:10

Похоже, что у 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 не был конкретным, но вы можете упомянуть об этом

Chad Ruppert 06.05.2009 18:54

Кроме того, это будет быстрее, чем регулярное выражение.

John Gietzen 08.05.2010 00:39

Хороший. Я бы сменил ReplaceString на Replace.

AMissico 25.07.2010 04:45

Согласитесь с комментариями выше. Это может быть преобразовано в метод расширения с тем же именем. Просто вставьте его в статический класс с сигнатурой метода: публичная статическая строка Replace (эта строка str, строка oldValue, строка newValue, сравнение StringComparison)

Mark Robinson 11.02.2011 19:50

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

Helge Klein 28.02.2011 16:58

@Helge, в общем, это может быть хорошо, но я должен принимать произвольные строки от пользователя и не могу рисковать, что ввод будет значимым для регулярного выражения. Конечно, я думаю, я мог бы написать цикл и поставить обратную косую черту перед каждым символом ... В этот момент я мог бы сделать то же самое (ИМХО).

Jim 03.05.2011 20:25

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

James Manning 12.02.2013 06:19

@JamesManning - Хм, интересно - не знал о Escape (). Спасибо.

Jim 13.02.2013 16:30

Во время модульного тестирования я столкнулся с тем, что он никогда не вернется, когда oldValue == newValue == "".

Ishmael 28.03.2013 23:10

В случае 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"); }

goodeye 08.04.2013 19:49

Здесь отличная работа. Я превратил его в метод расширения, но, что более важно, я добавил ускорение вверху в случае, когда str не содержит oldValue. Просто переместите int index = str.IndexOf(oldValue, comparison); в первую строку метода и верните str, если index == -1

Walden Leverich 07.08.2013 20:27

Это глючит; ReplaceString("œ", "oe", "", StringComparison.InvariantCulture) выкидывает ArgumentOutOfRangeException.

Michael Liu 22.12.2013 23:03

Просто изучите Regex, держите код в чистоте. Это тривиальный пример, но все же выглядит сложным. К сожалению, людям, одержимым скоростью, а затем пишущим такой убогий код, не повезло.

Jaycee 14.01.2014 15:36

@Jaycee, необходимость экранирования строки замены по умолчанию не выглядит для меня чистым кодом. Также я уверен, что реальная реализация Regex выглядит намного сложнее и, вероятно, имела множество ошибок в своих начальных версиях. Я очень надеюсь, что будет опубликована последняя версия без ошибок.

crokusek 16.05.2014 20:54

@crokusek Я широко использую регулярные выражения и замеченных мною ошибок не обнаружил. У вас гораздо больше шансов ввести ошибку с таким пользовательским кодом.

Jaycee 19.06.2014 18:33

Использование StringBuilder таким образом, скорее всего, не улучшит производительность так, как вы предполагали; он будет инициализирован 16-символьным буфером, и ваш цикл потенциально вызовет ряд выделений памяти и копий. Вы должны инициализировать ваш StringBuilder до подходящей емкости, прежде чем вы начнете добавлять к нему строки.

do0g 08.12.2014 03:24

@MichaelLiu Что ты думаешь о if (oldValue.Length > str.Length) return str; ... Это могло вызвать какие-то странные вещи. Я написал несколько тестов, все используют OrdinalIgnoreCase, и этот обходной путь ни в одном из них не сломался. Конечно, я могу упустить некоторые случаи, так что вы думаете?

Galilyou 09.12.2015 09:58

@MichaelLiu Вот тесты для этого Замени gist.github.com/Galilyou/00dcd0dab2d2a050c30c

Galilyou 09.12.2015 10:09

@Galilyou: Проблема, на которую я указал, не связана с проверкой длины; проблема связана с IndexOf и StringComparison.InvariantCulture.

Michael Liu 10.12.2015 18:18

примечание: ReplaceString("","","",StringComparison.CurrentCulture) приведет к бесконечному циклу!

Julian 08.05.2018 23:24

@WaldenLeverich Значит, вы избегаете создания экземпляра StringBuilder, но вводите оператор if? Мне кажется, это микрооптимизация (если что).

Caltor 10.04.2019 11:41

@Caltor Это не только конструктор, но и копия строки после while (), а затем .ToString обратно в строку. Эти вещи складываются. Но что еще более важно, разработчик может быстро увидеть, что произойдет, если совпадения нет. Кстати, проверьте код MS на наличие аналогичных проверок, а также проверки быстрого выхода.

Walden Leverich 17.04.2019 16:39

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

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

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

Оно работает. Вам нужно добавить ссылку на сборку Microsoft.VisualBasic.

CleverPatrick 02.08.2013 19:30

Странно, что у этого метода были проблемы, когда я его использовал (пропали символы в начале строки). Самый популярный ответ здесь от C. Dragon 76 работал, как ожидалось.

Jeremy Thompson 24.04.2015 08:35

Проблема в том, что он возвращает НОВУЮ строку, даже если замена не производится, где string.replace () возвращает указатель на ту же строку. Может стать неэффективным, если вы делаете что-то вроде слияния писем.

Brain2000 01.08.2015 03:48

Brain2000, вы ошибаетесь. Все строки в .NET неизменяемы.

Der_Meister 28.09.2017 09:25

Der_Meister, хотя то, что вы говорите, правильно, это не означает, что Brain2000 сказал неправильно.

Simon Hewitt 19.07.2018 20:19

версия, аналогичная версии 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;
    }

}

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

Vad 07.01.2015 21:01

Несколько ошибок в этом решении: 1. Проверьте исходные строки, oldValue и newValue на null. 2. Не возвращайте orginalString (не работает, простые типы не передаются по ссылке), а сначала присвойте значение orginalValue новой строке, измените ее и верните.

RWC 07.01.2016 16:34
    /// <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 25.11.2013 11:38

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

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

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 не обязательно.

Примечание: Однако вам нужно экранировать $ в новом значении, которое вы вставляете если они являются частью маркера "захваченного значения". Таким образом, три знака доллара в 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?

Aheho 15.08.2014 05:03

Если есть разница в длине между oldValue и newValue, строка будет становиться длиннее или короче по мере замены значений. match.Index относится к исходному местоположению в строке, нам нужно скорректировать перемещение этих позиций из-за нашей замены. Другой подход - выполнить Remove / Insert справа налево.

Brandon 15.08.2014 17:26

Я понимаю. Для этого и нужна переменная "смещение". Я не понимаю, почему вы умножаете на matchNo. Моя интуиция подсказывает мне, что расположение совпадения в строке не имеет никакого отношения к фактическому количеству предыдущих вхождений.

Aheho 15.08.2014 20:29

Неважно, теперь я понял. Смещение необходимо масштабировать в зависимости от количества вхождений. Если вы теряете 2 символа каждый раз, когда вам нужно выполнить замену, вам необходимо учитывать это при вычислении параметров для метода удаления.

Aheho 15.08.2014 20:35

Вдохновленный ответом 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 за то, что не использует регулярное выражение, когда это не нужно. Конечно, вы используете еще несколько строк кода, но это намного эффективнее, чем замена на основе регулярных выражений, если вам не нужна функциональность $.

ChrisG 17.06.2016 23:34

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

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"

[1] https://docs.microsoft.com/en-us/dotnet/api/system.string.replace#System_String_Replace_System_String_System_String_System_StringComparison_

Другие вопросы по теме