String.Заменить регистр игнорирования - PullRequest
165 голосов
/ 08 июня 2011

У меня есть строка с именем "hello world"

Мне нужно заменить слово "world" на "csharp"

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

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

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

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

Ответы [ 15 ]

262 голосов
/ 08 июня 2011

Вы можете использовать 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"
    }
}
91 голосов
/ 08 июня 2011
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 }
27 голосов
/ 26 февраля 2016

Множество предложений с использованием 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;
}
27 голосов
/ 13 октября 2013

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

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;
    }
}
25 голосов
/ 18 августа 2017

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

/// <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, поэтому это не так универсальный метод

@ Майк Стиллион: есть проблема с этим кодом. Если текст в новом является надмножеством текста в старом, это может привести к бесконечному циклу.


Сравнительный тест : это решение в 2,59X раз быстрее, чем регулярное выражение из @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.

13 голосов
/ 10 апреля 2017

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

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)
5 голосов
/ 30 марта 2016

Изменен ответ @ 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;
}
5 голосов
/ 08 июня 2011

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

Взято из здесь :

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

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

2 голосов
/ 23 апреля 2017

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

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;
    }
1 голос
/ 22 декабря 2016

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

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

Однако без выполнения экранирования вы получите следующее, что не является ожидаемым поведением от 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...