24

I recently had to perform some string replacements in .net and found myself developing a regular expression replacement function for this purpose. After getting it to work I couldn't help but think there must be a built in case insensitive replacement operation in .Net that I'm missing?

Surely when there are so many other string operations that support case insensitive comparission such as;

var compareStrings  = String.Compare("a", "b", blIgnoreCase);
var equalStrings    = String.Equals("a", "b", StringComparison.CurrentCultureIgnoreCase);

then there must be a built in equivalent for replace?

Richard
  • 106,783
  • 21
  • 203
  • 265
Brian Scott
  • 9,221
  • 6
  • 47
  • 68
  • Have a look at the discussion here: http://stackoverflow.com/questions/244531/is-there-an-alternative-to-string-replace-that-is-case-insensitive. There's an example Extension method which will do what you want. – RB. Apr 05 '11 at 09:04
  • @RB: Thanks, that's a nice way of "extending" the .Net functionality but my query was about whether there was a built in method. I will use this example though to wrap my Regex replace, cheers. – Brian Scott Apr 05 '11 at 09:27

9 Answers9

25

Found one in the comments here: http://www.codeproject.com/Messages/1835929/this-one-is-even-faster-and-more-flexible-modified.aspx

static public string Replace(string original, string pattern, string replacement, StringComparison comparisonType)
{
     return Replace(original, pattern, replacement, comparisonType, -1);
}

static public string Replace(string original, string pattern, string replacement, StringComparison comparisonType, int stringBuilderInitialSize)
{
     if (original == null)
     {
         return null;
     }

     if (String.IsNullOrEmpty(pattern))
     {
         return original;
     }


     int posCurrent = 0;
     int lenPattern = pattern.Length;
     int idxNext = original.IndexOf(pattern, comparisonType);
     StringBuilder result = new StringBuilder(stringBuilderInitialSize < 0 ? Math.Min(4096, original.Length) : stringBuilderInitialSize);

     while (idxNext >= 0)
     {
        result.Append(original, posCurrent, idxNext - posCurrent);
        result.Append(replacement);

        posCurrent = idxNext + lenPattern;

        idxNext = original.IndexOf(pattern, posCurrent, comparisonType);
      }

      result.Append(original, posCurrent, original.Length - posCurrent);

      return result.ToString();
}

Should be the fastest, but i haven't checked.

Otherwise you should do what Simon suggested and use the VisualBasic Replace function. This is what i often do because of its case-insensitive capabilities.

string s = "SoftWare";
s = Microsoft.VisualBasic.Strings.Replace(s, "software", "hardware", 1, -1, Constants.vbTextCompare);

You have to add a reference to the Microsoft.VisualBasic dll.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Thanks for the comprehensive breakdown. So it looks like there's no built in .Net mechanism, at least rolling my own doesn't feel so wrong now. Cheers. – Brian Scott Apr 05 '11 at 09:28
  • 1
    Be careful with non-ordinal comparison types: The actual part to replace might not actually have the same length as `oldValue`, because of things like `æ` matching `AE` or `ß` matching `SS`... And I don't know of an equivalent to `IndexOf()` that would return the length of the actually matched part... – Medinoc Sep 18 '14 at 13:25
  • 6
    @BrianScott: The VisualBasic names space IS part of the .net runtime, it is not limited to just VB. It is fine to reference it from a C# (or F#) project. It is named that for historical reason, but it doesn't have cooties. – jmoreno Apr 11 '15 at 21:09
16

It's not ideal, but you can import Microsoft.VisualBasic and use Strings.Replace to do this. Otherwise I think it's case of rolling your own or stick with Regular Expressions.

Simon Steele
  • 11,558
  • 4
  • 45
  • 67
4

Update in .NET Core 2.0+ (August 2017)

This is natively available in .NET Core 2.0+ with String.Replace which has the following overloads

public string Replace (string oldValue, string newValue, StringComparison comparisonType);
public string Replace (string oldValue, string newValue, bool ignoreCase, System.Globalization.CultureInfo culture);

So you can use either like this:

"A".Replace("a", "b", StringComparison.CurrentCultureIgnoreCase);
"A".Replace("a", "b", true, CultureInfo.CurrentCulture);

PS: You can browse .NET Core source if you want to see how MS implemented it


Legacy .NET Framework 4.8- option for VB Projects

Visual Basic has an Option Compare setting which can be set to Binary or Text

Setting to Text will make all string comparisons across your project case insensitive by default.

So, as other answers have suggested, if you are pulling in the Microsoft.VisualBasic.dll, when calling Strings.Replace if you don't explicitly pass a CompareMethod the method will actually defer to the Compare option for your file or project using the [OptionCompare] Parameter Attribute

So either of the following will also work (top option only available in VB , but both rely on VisualBasic.dll)

Option Compare Text

Replace("A","a","b")
Replace("A","a","b", Compare := CompareMethod.Text)
KyleMit
  • 30,350
  • 66
  • 462
  • 664
4

Here's an extension method. Not sure where I found it.

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

}
rboarman
  • 8,248
  • 8
  • 57
  • 87
  • 1
    This code lacks of some important checks (infinite loop if oldValue is empty). – Eric Boumendil Oct 21 '16 at 12:35
  • An infinite loop also occurs if originalString and oldValue are both empty. If originalString is empty then there is nothing to replace so there needs to be a check for originalString being empty. Also need to check for null strings. Otherwise this works well. I have translated to VB.NET and posted as an answer. – Guru Josh Nov 11 '16 at 02:14
  • not to mention returning "originalString" is awful for code readability... – MensSana Mar 23 '17 at 13:38
  • @KyleMit sorry for the feeling mate. And no, `originalString` is not a good name (« code readability » speaking) when returning a modified string... May I suggest you a good reading: Clean Code by RC Martin – MensSana Apr 28 '19 at 02:07
2

This is a VB.NET adaptation of rboarman's answer above with the necessary checks for null and empty strings to avoid an infinite loop.

Public Function Replace(ByVal originalString As String, 
                        ByVal oldValue As String, 
                        ByVal newValue As String, 
                        ByVal comparisonType As StringComparison) As String
    If Not String.IsNullOrEmpty(originalString) AndAlso 
       Not String.IsNullOrEmpty(oldValue) AndAlso 
       newValue IsNot Nothing Then
        Dim startIndex As Int32

        Do While True
            startIndex = originalString.IndexOf(oldValue, startIndex, comparisonType)
            If startIndex = -1 Then Exit Do
            originalString = originalString.Substring(0, startIndex) & newValue & 
                             originalString.Substring(startIndex + oldValue.Length)
            startIndex += newValue.Length
        Loop
    End If

    Return originalString
End Function
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Guru Josh
  • 566
  • 8
  • 16
1

You can use Microsoft.VisualBasic.Strings.Replace and pass in Microsoft.VisualBasic.CompareMethod.Text to do a case insensitive replace like this:

Dim myString As String = "One Two Three"
myString = Replace(myString, "two", "TWO", Compare:= CompareMethod.Text)
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Pankti Shah
  • 133
  • 1
  • 4
1

My 2 cents:

public static string Replace(this string originalString, string oldValue, string newValue, StringComparison comparisonType)
{
    if (originalString == null)
        return null;
    if (oldValue == null)
        throw new ArgumentNullException("oldValue");
    if (oldValue == string.Empty)
        return originalString;
    if (newValue == null)
        throw new ArgumentNullException("newValue");

    const int indexNotFound = -1;
    int startIndex = 0, index = 0;
    while ((index = originalString.IndexOf(oldValue, startIndex, comparisonType)) != indexNotFound)
    {
        originalString = originalString.Substring(0, index) + newValue + originalString.Substring(index + oldValue.Length);
        startIndex = index + newValue.Length;
    }

    return originalString;
}



Replace("FOOBAR", "O", "za", StringComparison.OrdinalIgnoreCase);
// "FzazaBAR"

Replace("", "O", "za", StringComparison.OrdinalIgnoreCase);
// ""

Replace("FOO", "BAR", "", StringComparison.OrdinalIgnoreCase);
// "FOO"

Replace("FOO", "F", "", StringComparison.OrdinalIgnoreCase);
// "OO"

Replace("FOO", "", "BAR", StringComparison.OrdinalIgnoreCase);
// "FOO"
Eric Boumendil
  • 2,318
  • 1
  • 27
  • 32
0

I know of no canned instance in the framework, but here's another extension method version with a minimal amount of statements (although maybe not the fastest), for fun. More versions of replacement functions are posted at http://www.codeproject.com/KB/string/fastestcscaseinsstringrep.aspx and "Is there an alternative to string.Replace that is case-insensitive?" as well.

public static string ReplaceIgnoreCase(this string alterableString, string oldValue, string newValue){
    if(alterableString == null) return null;
    for(
        int i = alterableString.IndexOf(oldValue, System.StringComparison.CurrentCultureIgnoreCase);
        i > -1;
        i = alterableString.IndexOf(oldValue, i+newValue.Length, System.StringComparison.CurrentCultureIgnoreCase)
    ) alterableString =
        alterableString.Substring(0, i)
        +newValue
        +alterableString.Substring(i+oldValue.Length)
    ;
    return alterableString;
}
Community
  • 1
  • 1
scradam
  • 1,053
  • 11
  • 11
0

A little off-topic maybe, but while case insensitive replacement is covered by the standard replace function since (?), here a little function that adds text before and/or after a substring without changing the case of the substring:

    Public Shared Function StrWrap(Haystack As String, Needle As String, Optional AddBefore As String = "", Optional AddAfter As String = "") As String
        Dim Index = Haystack.ToLower.IndexOf(Needle.ToLower)
        If Index < 0 Then Return Haystack
        Return Haystack.Substring(0, Index) + AddBefore + Haystack.Substring(Index, Needle.Length) + AddAfter + Haystack.Substring(Index + Needle.Length)
    End Function

Usage: StrWrap("Hello World Test", "hello", "Hi, ", " and Ahoi")

Result: "Hi, Hello and Ahoi World Test"


Standard case insensitive replacement:

Replace("Hello World Test", "hello", "Hi", , Compare:=CompareMethod.Text)