String.Заменить регистр игнорирования


214

У меня есть строка под названием "Привет, мир"

Мне нужно заменить слово «мир» на «csharp»

для этого я использую:

string.Replace("World", "csharp");

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

Есть ли способ избежать этой чувствительности к регистру в методе string.Replace?



Ответы:


309

Вы можете использовать Regex и выполнить замену без учета регистра:

class Program
{
    static void Main()
    {
        string input = "hello WoRlD";
        string result = 
           Regex.Replace(input, "world", "csharp", RegexOptions.IgnoreCase);
        Console.WriteLine(result); // prints "hello csharp"
    }
}

19
Не работает с элементами языка Regex , поэтому это не универсальный метод. Ответ Стива Б правильный.
AsValeO

1
Так что лучше не писать hello. world?или что-либо еще, содержащее операторы регулярных выражений.
Себастьян Мах

На всякий случай, если кто-то не был склонен читать дальше, это был принятый ответ в 2011 году и имеет огромное количество голосов. Это прекрасно работает, если вам нужно только заменить буквенно-цифровую. Однако, если вам нужно заменить знаки препинания, у вас могут возникнуть большие проблемы. Ответ Олега Заревенного является превосходным, но имеет только небольшое количество голосов, потому что он был опубликован в 2017 году.
Тони Пулокас

115
var search = "world";
var replacement = "csharp";
string result = Regex.Replace(
    stringToLookInto,
    Regex.Escape(search), 
    replacement.Replace("$","$$"), 
    RegexOptions.IgnoreCase
);

Regex.Escape полезно , если вы полагаетесь на пользовательский ввод , который может содержит элементы языка Regex

Обновить

Благодаря комментариям вам на самом деле не нужно экранировать строку замены.

Вот небольшая скрипка, которая проверяет код :

using System;
using System.Text.RegularExpressions;           
public class Program
{
    public static void Main()
    {

        var tests = new[] {
            new { Input="abcdef", Search="abc", Replacement="xyz", Expected="xyzdef" },
            new { Input="ABCdef", Search="abc", Replacement="xyz", Expected="xyzdef" },
            new { Input="A*BCdef", Search="a*bc", Replacement="xyz", Expected="xyzdef" },
            new { Input="abcdef", Search="abc", Replacement="x*yz", Expected="x*yzdef" },       
            new { Input="abcdef", Search="abc", Replacement="$", Expected="$def" },
        };


        foreach(var test in tests){
            var result = ReplaceCaseInsensitive(test.Input, test.Search, test.Replacement);

            Console.WriteLine(
                "Success: {0}, Actual: {1}, {2}",
                result == test.Expected,
                result,
                test
            );

        }


    }

    private static string ReplaceCaseInsensitive(string input, string search, string replacement){
        string result = Regex.Replace(
            input,
            Regex.Escape(search), 
            replacement.Replace("$","$$"), 
            RegexOptions.IgnoreCase
        );
        return result;
    }
}

Его вывод:

Success: True, Actual: xyzdef, { Input = abcdef, Search = abc, Replacement = xyz, Expected = xyzdef } 
Success: True, Actual: xyzdef, { Input = ABCdef, Search = abc, Replacement = xyz, Expected = xyzdef }
Success: True, Actual: xyzdef, { Input = A*BCdef, Search = a*bc, Replacement = xyz, Expected = xyzdef } 
Success: True, Actual: x*yzdef, { Input = abcdef, Search = abc, Replacement = x*yz, Expected = x*yzdef} 
Success: True, Actual: $def, { Input = abcdef, Search = abc, Replacement = $, Expected = $def }

2
Этот метод завершается ошибкой, если вместо replace = "! @ # $% ^ & * ()" Вы получаете "! @ \ # \ $% \ ^ & * ()".
Kcoder

2
Во-вторых, Regex.Escapeэто плохо, это будет префикс специальных символов с обратной косой чертой. Похоже, что лучший способ - это .Replace ("$", "$$"), что довольно глупо ( stackoverflow.com/a/10078353 ).
Дэнни Таппени

1
@dannyTuppeny: вы правы ... Я обновил ответ соответственно
Стив B

54

2.5X БЫСТРЫЙ и НАИБОЛЕЕ ЭФФЕКТИВНЫЙ метод, чем другие методы регулярных выражений:

/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another 
/// specified string according the type of search to use for the specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrences of <paramref name="oldValue"/>. 
/// If value is equal to <c>null</c>, than all occurrences of <paramref name="oldValue"/> will be removed from the <paramref name="str"/>.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param>
/// <returns>A string that is equivalent to the current string except that all instances of <paramref name="oldValue"/> are replaced with <paramref name="newValue"/>. 
/// If <paramref name="oldValue"/> is not found in the current instance, the method returns the current instance unchanged.</returns>
[DebuggerStepThrough]
public static string Replace(this string str,
    string oldValue, string @newValue,
    StringComparison comparisonType)
{

    // Check inputs.
    if (str == null)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentNullException(nameof(str));
    }
    if (str.Length == 0)
    {
        // Same as original .NET C# string.Replace behavior.
        return str;
    }
    if (oldValue == null)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentNullException(nameof(oldValue));
    }
    if (oldValue.Length == 0)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentException("String cannot be of zero length.");
    }


    //if (oldValue.Equals(newValue, comparisonType))
    //{
    //This condition has no sense
    //It will prevent method from replacesing: "Example", "ExAmPlE", "EXAMPLE" to "example"
    //return str;
    //}



    // Prepare string builder for storing the processed string.
    // Note: StringBuilder has a better performance than String by 30-40%.
    StringBuilder resultStringBuilder = new StringBuilder(str.Length);



    // Analyze the replacement: replace or remove.
    bool isReplacementNullOrEmpty = string.IsNullOrEmpty(@newValue);



    // Replace all values.
    const int valueNotFound = -1;
    int foundAt;
    int startSearchFromIndex = 0;
    while ((foundAt = str.IndexOf(oldValue, startSearchFromIndex, comparisonType)) != valueNotFound)
    {

        // Append all characters until the found replacement.
        int @charsUntilReplacment = foundAt - startSearchFromIndex;
        bool isNothingToAppend = @charsUntilReplacment == 0;
        if (!isNothingToAppend)
        {
            resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilReplacment);
        }



        // Process the replacement.
        if (!isReplacementNullOrEmpty)
        {
            resultStringBuilder.Append(@newValue);
        }


        // Prepare start index for the next search.
        // This needed to prevent infinite loop, otherwise method always start search 
        // from the start of the string. For example: if an oldValue == "EXAMPLE", newValue == "example"
        // and comparisonType == "any ignore case" will conquer to replacing:
        // "EXAMPLE" to "example" to "example" to "example" … infinite loop.
        startSearchFromIndex = foundAt + oldValue.Length;
        if (startSearchFromIndex == str.Length)
        {
            // It is end of the input string: no more space for the next search.
            // The input string ends with a value that has already been replaced. 
            // Therefore, the string builder with the result is complete and no further action is required.
            return resultStringBuilder.ToString();
        }
    }


    // Append the last part to the result.
    int @charsUntilStringEnd = str.Length - startSearchFromIndex;
    resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilStringEnd);


    return resultStringBuilder.ToString();

}

Примечание: игнорируйте регистр == в StringComparison.OrdinalIgnoreCaseкачестве параметра для StringComparison comparisonType. Это самый быстрый способ учета всех значений без учета регистра.


Преимущества этого метода:

  • Высокая эффективность процессора и памяти;
  • Это самое быстрое решение, в 2,5 раза быстрее других методов с регулярными выражениями (доказательство в конце);
  • Подходит для удаления деталей из входной строки (установлено newValueна null), оптимизировано для этого;
  • То же, что и оригинальное поведение .NET C # string.Replace , те же исключения;
  • Хорошо прокомментировано, легко понять;
  • Проще - без регулярных выражений. Регулярные выражения всегда медленнее из-за их универсальности (даже скомпилированы);
  • Этот метод хорошо протестирован, и в других решениях нет скрытых недостатков, таких как бесконечный цикл, даже высоко оцененных:

@AsValeO: не работает с элементами языка Regex, поэтому это не универсальный метод

@Mike Stillion: есть проблема с этим кодом. Если текст в new является надмножеством текста в old, это может создать бесконечный цикл.


Эталонный тест : это решение в 2,59 раза быстрее, чем регулярное выражение из @Steve B., код:

// Results:
// 1/2. Regular expression solution: 4486 milliseconds
// 2/2. Current solution: 1727 milliseconds — 2.59X times FASTER! than regex!

// Notes: the test was started 5 times, the result is an average; release build.

const int benchmarkIterations = 1000000;
const string sourceString = "aaaaddsdsdsdsdsd";
const string oldValue = "D";
const string newValue = "Fod";
long totalLenght = 0;

Stopwatch regexStopwatch = Stopwatch.StartNew();
string tempString1;
for (int i = 0; i < benchmarkIterations; i++)
{
    tempString1 = sourceString;
    tempString1 = ReplaceCaseInsensitive(tempString1, oldValue, newValue);

    totalLenght = totalLenght + tempString1.Length;
}
regexStopwatch.Stop();



Stopwatch currentSolutionStopwatch = Stopwatch.StartNew();
string tempString2;
for (int i = 0; i < benchmarkIterations; i++)
{
    tempString2 = sourceString;
    tempString2 = tempString2.Replace(oldValue, newValue,
        StringComparison.OrdinalIgnoreCase);

    totalLenght = totalLenght + tempString2.Length;
}
currentSolutionStopwatch.Stop();

Оригинальная идея - @ Darky711; спасибо @MinerR за StringBuilder.


5
Бьюсь об заклад, вы можете сделать это еще быстрее, используя StringBuilder, а не строку.
MineR

1
@MineR Вы правы, я просто обновил решение @ Darky711 без бесконечного цикла, поэтому я использовал String. Тем не менее, StringBuilderэто действительно быстрее на 30-40%, чем String. Я обновил решение. Спасибо;)
Олег Заревенный

2
Интересный подход. Вероятно, лучше (лучше, чем у меня :)), когда производительность имеет значение. Обычно это метод для добавления в общую библиотеку общего кода.
Стив Б

2
Использование выражений nameof делает это действительным только для C # 6.0 и выше. Если вы в VS2013, вы можете использовать его, просто удалив операнды в исключениях.
LanchPad

Для закомментированного "// if (oldValue.Equals (newValue, сравнениеType))" "заменить тип сравнения на StringComparison.Ordinal?
Роджер Уиллкокс

31

Расширения делают нашу жизнь проще:

static public class StringExtensions
{
    static public string ReplaceInsensitive(this string str, string from, string to)
    {
        str = Regex.Replace(str, from, to, RegexOptions.IgnoreCase);
        return str;
    }
}

10
А побег делает нашу жизнь менее глючной :-) return Regex.Replace (input, Regex.Escape (search), replace.Replace ("$", "$$"), RegexOptions.IgnoreCase);
Vman

29

Много предложений с использованием Regex. Как насчет этого метода расширения без него:

public static string Replace(this string str, string old, string @new, StringComparison comparison)
{
    @new = @new ?? "";
    if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(old) || old.Equals(@new, comparison))
        return str;
    int foundAt = 0;
    while ((foundAt = str.IndexOf(old, foundAt, comparison)) != -1)
    {
        str = str.Remove(foundAt, old.Length).Insert(foundAt, @new);
        foundAt += @new.Length;
    }
    return str;
}

Обратите внимание, что аргумент сравнения не используется для фактической замены (он всегда нечувствителен к регистру)
Боло

2
Существует проблема с этим кодом. Если текст в new является надмножеством текста в old , это может создать бесконечный цикл. После того, как новое добавлено в FoundAt , значение FoundAt должно быть увеличено на длину нового .
Майк Стиллион,

comparisonпараметр должен быть использован в IndexOf, вместоStringComparison.CurrentCultureIgnoreCase
Maxence

@Bolo Я отредактировал его, чтобы использовать аргумент сравнения (может потребоваться некоторое время для его рецензирования).
bradlis7

2
Я бы также отделил это условие для возврата новой строки:, if(old.Equals(@new, comparison)) return @new;поскольку новая строка может отличаться в верхнем / нижнем регистре.
sɐunıɔ ןɐ qɐp

13

Вы можете использовать пространство имен Microsoft.VisualBasic, чтобы найти эту вспомогательную функцию:

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)

Я гордился своим ответом, пока не увидел этот, который является лучшим ответом, потому что он встроен. Например: Strings.Replace ("TeStInG123", "t", "z", 1, -1, CompareMethod.Text) возвращает " zeSzInG123 "
Боло

Предупреждение, Strings.Replace возвращает ноль, если искомая строка является пустой строкой.
Мафу Джош

1
В .Net 4.7.2 вам нужно добавить ссылку на Microsoft.VisualBasic, чтобы заставить это работать. В .Net Core класс Microsoft.VisualBasic.Strings (в любом случае в версии 10.3.0) не реализует функцию замены. Это работает и в Powershell, если вы сначала добавили класс -AssemblyName Microsoft.VisualBasic.
Профессор фон Лемонгаргл

6

( Отредактировано: не знал о проблеме «голой ссылки», извините за это)

Взято отсюда :

string myString = "find Me and replace ME";
string strReplace = "me";
myString = Regex.Replace(myString, "me", strReplace, RegexOptions.IgnoreCase);

Кажется, вы не первый, кто жаловался на отсутствие строки без учета регистра. Замените.


5

Изменил ответ @ Darky711, чтобы использовать переданный тип сравнения и сопоставить структуру, как можно точнее заменить имена и комментарии xml.

/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrances of oldValue.</param>
/// <param name="comparisonType">Type of the comparison.</param>
/// <returns></returns>
public static string Replace(this string str, string oldValue, string @newValue, StringComparison comparisonType)
{
    @newValue = @newValue ?? string.Empty;
    if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(oldValue) || oldValue.Equals(@newValue, comparisonType))
    {
        return str;
    }
    int foundAt;
    while ((foundAt = str.IndexOf(oldValue, 0, comparisonType)) != -1)
    {
        str = str.Remove(foundAt, oldValue.Length).Insert(foundAt, @newValue);
    }
    return str;
}

2

Я написал метод расширения:

public static string ReplaceIgnoreCase(this string source, string oldVale, string newVale)
    {
        if (source.IsNullOrEmpty() || oldVale.IsNullOrEmpty())
            return source;

        var stringBuilder = new StringBuilder();
        string result = source;

        int index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);

        while (index >= 0)
        {
            if (index > 0)
                stringBuilder.Append(result.Substring(0, index));

            if (newVale.IsNullOrEmpty().IsNot())
                stringBuilder.Append(newVale);

            stringBuilder.Append(result.Substring(index + oldVale.Length));

            result = stringBuilder.ToString();

            index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        }

        return result;
    }

Я использую два дополнительных метода расширения для предыдущего метода расширения:

    public static bool IsNullOrEmpty(this string value)
    {
        return string.IsNullOrEmpty(value);
    }

    public static bool IsNot(this bool val)
    {
        return val == false;
    }

2
Upvoted. Но IsNotотносится к расширениям слишком серьезно :)
Nawfal

Разочаровывает, это работает не во всех ситуациях. Я передавал отличительное имя, и оно добавлялось до тех пор, пока строка не стала длиной в миллион символов, а затем не хватило памяти
Bbb

Альтернатива, предложенная ниже, исправила мою проблему
Bbb

Мне очень нравится.IsNot
ttugates

1

Расширение ответа Петруччо с помощью Regex.Escapeстроки поиска и выход из подобранной группы, как предложено в ответе Стива Б. (и некоторые незначительные изменения на мой вкус):

public static class StringExtensions
{
    public static string ReplaceIgnoreCase(this string str, string from, string to)
    {
        return Regex.Replace(str, Regex.Escape(from), to.Replace("$", "$$"), RegexOptions.IgnoreCase);
    }
}

Который даст следующие ожидаемые результаты:

Console.WriteLine("(heLLo) wOrld".ReplaceIgnoreCase("(hello) world", "Hi $1 Universe")); // Hi $1 Universe
Console.WriteLine("heLLo wOrld".ReplaceIgnoreCase("(hello) world", "Hi $1 Universe"));   // heLLo wOrld

Однако, не выполняя экранирование, вы получите следующее, что не является ожидаемым поведением от a, String.Replaceкоторое не зависит от регистра:

Console.WriteLine("(heLLo) wOrld".ReplaceIgnoreCase_NoEscaping("(hello) world", "Hi $1 Universe")); // (heLLo) wOrld
Console.WriteLine("heLLo wOrld".ReplaceIgnoreCase_NoEscaping("(hello) world", "Hi $1 Universe"));   // Hi heLLo Universe

1

Разве это не работает: я не могу представить, чтобы что-то еще было намного быстрее или проще.

public static class ExtensionMethodsString
{
    public static string Replace(this String thisString, string oldValue, string newValue, StringComparison stringComparison)
    {
        string working = thisString;
        int index = working.IndexOf(oldValue, stringComparison);
        while (index != -1)
        {
            working = working.Remove(index, oldValue.Length);
            working = working.Insert(index, newValue);
            index = index + newValue.Length;
            index = working.IndexOf(oldValue, index, stringComparison);
        }
        return working;
    }
}

Я не знаю, если это быстрее, но это сжато, не использует регулярные выражения и потенциальные проблемы и использует встроенный StringComparison.
fvlinden

0

Функция ниже должна удалить все совпадающие слова как (this) из набора строк. Равикант Сонаре.

private static void myfun()
{
    string mystring = "thiTHISThiss This THIS THis tThishiThiss. Box";
    var regex = new Regex("this", RegexOptions.IgnoreCase);
    mystring = regex.Replace(mystring, "");
    string[] str = mystring.Split(' ');
    for (int i = 0; i < str.Length; i++)
    {
        if (regex.IsMatch(str[i].ToString()))
        {
            mystring = mystring.Replace(str[i].ToString(), string.Empty);

        }
    }
    Console.WriteLine(mystring);
}

Эта функция заменяет всю строку из набора строк ... Ravikant Sonare,
Ravikant Sonare

0

При использовании решения @Georgy Batalov у меня возникла проблема при использовании следующего примера

строка оригинал = "бла, DC = бле, DC = блих, DC = бло, DC = ком"; замененная строка = original.ReplaceIgnoreCase (", DC =", ".")

Ниже как я переписал его расширение

public static string ReplaceIgnoreCase(this string source, string oldVale, 
string newVale)
    {
        if (source.IsNullOrEmpty() || oldVale.IsNullOrEmpty())
            return source;

        var stringBuilder = new StringBuilder();
        string result = source;

        int index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        bool initialRun = true;

        while (index >= 0)
        {
            string substr = result.Substring(0, index);
            substr = substr + newVale;
            result = result.Remove(0, index);
            result = result.Remove(0, oldVale.Length);

            stringBuilder.Append(substr);

            index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        }

        if (result.Length > 0)
        {
            stringBuilder.Append(result);
        }

        return stringBuilder.ToString();
    }

0

ниже - альтернатива для замены строки, игнорируя регистр символов

String thisString = "hello world"; 
String replaceString = "World";

//thisString.Replace("World", "csharp"); 
//below is the alternative to replace string ignoring character case

int start = StringUtils.indexOfIgnoreCase(thisString,replaceString);
String searchKey = thisString.substring(start, start+replaceString.length());
thisString= thisString.replaceAll(searchKey ,replaceString );
System.out.println(thisString);

//prints hello World

0

Вы также можете попробовать Regexкласс.

var regex = new Regex( "camel", RegexOptions.IgnoreCase ); var newSentence = regex.Replace( sentence, "horse" );


-3

Я предпочитаю это - "Hello World" .ToLower (). Replace ("world", "csharp");


1
Это будет строчными буквами все, даже слова, которые не должны были быть заменены.
JJJ

Очевидно, что вы можете использовать это, только если вы не обеспокоены делом.
Харшал
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.