You ever do this stunt? You're in the middle of working and you realize that you need a string function. .NET makes string manipulation fairly easy but it can often be frustrating to find just the right function. The issue is that .NET string functions are scattered over several different objects. The string class is what is used most frequently but it's not a terrible complete set of string manipulation functions.

So I just needed a routine to replace the 2nd instance of a particular string match in a string. So how many ways can I do this? Actually just using a base library function - nothing directly.

So you might think about:

  • string.Replace()
  • StringBuilder.Replace()
  • Regex.Replace()
  • Regex.Replace()  (Instance)

Lots of choices for string functionality in general and Replace functionality in particular. String.Replace() works for plain string replacements, but it won't help with case sensitivity or replacement of a specific instance of text.

StringBuilder.Replace() is a little better - it lets you specify WHERE to replace a string optionally, but you have to provide an index into the string. Since IndexOf() doesn't let you jump to a specific instance that's not really all that useful either. Regex.Replace() - well anything's possible with pattern matches, but in order to deal with a specific instance replacement you'd have to use a MatchEvaluator which is not the greatest way for building say a generic routine. In that scenario Regex always feels to application level.

Then there's also the Regex.Replace at the instance level. Yup you can create a Regex instance and you get a different set of functions than the static ones (another odd choice for API design). This version of regExInstance.Replace() lets you control the max number of replacements that occur and a start position, but not the actual start instance.

So lots of options but all close, but no cigar.

So after rummaging through all this I finally just created a couple of generic routines that do what I need.

[updated: 4/30/07 with feedback from comments]

 
/// <summary>
/// String replace function that support
/// </summary>
/// <param name="OrigString">Original input string</param>
/// <param name="FindString">The string that is to be replaced</param>
/// <param name="ReplaceWith">The replacement string</param>
/// <param name="Instance">Instance of the FindString that is to be found. if Instance = -1 all are replaced</param>
/// <param name="CaseInsensitive">Case insensitivity flag</param>
/// <returns>updated string or original string if no matches</returns>
public static string ReplaceStringInstance(string OrigString, string FindString, 
                                           string ReplaceWith, int Instance, 
                                           bool CaseInsensitive)
{
    if (Instance == -1)
        return ReplaceString(OrigString, FindString, ReplaceWith, CaseInsensitive);
 
    int at1 = 0;            
    for (int x = 0; x < Instance; x++)
    {
 
        if (CaseInsensitive)
            at1 = OrigString.IndexOf(FindString, at1, OrigString.Length - at1,StringComparison.OrdinalIgnoreCase);
        else
            at1 = OrigString.IndexOf(FindString, at1);
 
        if (at1 == -1)
            return OrigString;
 
        if (x < Instance-1)
            at1 += FindString.Length;
    }            
 
    return OrigString.Substring(0, at1) + ReplaceWith + OrigString.Substring(at1 + FindString.Length);
 
    //StringBuilder sb = new StringBuilder(OrigString); 
    //sb.Replace(FindString, ReplaceString, at1, FindString.Length); 
    //return sb.ToString();
}
 
/// <summary>
/// Replaces a substring within a string with another substring with optional case sensitivity turned off.
/// </summary>
/// <param name="OrigString">String to do replacements on</param>
/// <param name="FindString">The string to find</param>
/// <param name="ReplaceString">The string to replace found string wiht</param>
/// <param name="CaseInsensitive">If true case insensitive search is performed</param>
/// <returns>updated string or original string if no matches</returns>
public static string ReplaceString(string OrigString, string FindString, 
                                   string ReplaceString, bool CaseInsensitive)
{
    int at1 = 0;
    while(true)
    {
        if (CaseInsensitive)
            at1 = OrigString.IndexOf(FindString,at1,OrigString.Length-at1,StringComparison.OrdinalIgnoreCase);
        else
            at1 = OrigString.IndexOf(FindString,at1);
 
        if (at1 == -1)
            return OrigString;
 
        OrigString = OrigString.Substring(0, at1) + ReplaceString + OrigString.Substring(at1 + FindString.Length);
 
        at1 += ReplaceString.Length;
    }
 
    return OrigString;
}

I'm sure some of the Regex wiz's are going to have a better way, but hey I'm glyph challenged and this works.

I also wonder frequently how efficient performance of RegEx is compared to using procedural code to do stuff like this. I would imagine writing code like this for simple string manipulations like the above is pretty efficient - I suspect RegEx would probably have some overhead especially for smaller strings and after all Regex has to run some sort of code to do its matching and replacement as well.

Thoughts?