An Extension method to replace multiple Chars in a string

I needed a Sanitizer function for a project, which will replace chars in a string with other chars. To my dismay, I found that there is only a function to replace a single char or string. Of course, I could chain multiple string.Replace calls, but that creates a new string every time which can add quite a bit of overhead. So I've created a function that takes two Lists of chars and then replaces each occurrence of one char with another one. This also allowed me to implement a second function I was missing from string.Replace, the ability to remove chars (it's possible if you use the string overloads, but not the char ones). The second List is of nullable char type, so just pass in null to remove a char.

public static string ReplaceMultiple(this string input,
                                     IDictionary<char,char?> replacements)
{
    var sb = new StringBuilder(input.Length);
    foreach (char c in input)
    {
        if (!replacements.ContainsKey(c))
        {
            sb.Append(c);
        }
        else
        {
            char? replacement = replacements[c];
            if (replacement.HasValue)
            {
                sb.Append(replacement.Value);
            }
        }
    }
    return sb.ToString();
}

Usage example:

public string SanitizeTitle(string title)
{
    // Replace space with _ and remove ? and :
    var replacements = new Dictionary<char, char?>
                           {
                               {' ', '_'},
                               {'?', null},
                               {':', null}
                           };
    return title.ReplaceMultiple(replacements);
}

Update: The previous version had two lists. When working with that, I found it rather stupid to keep two lists in sync (they need to have the same count, and the order needs to be correct). So this updated version uses a Dictionary instead, which also allowed me to remove the Exception.

Important caveat: Each character is only processed once, so if you replace a char and happen to have a replacement for the replacement, it will not be processed. Example:

public string ImportantCaveat()
{
    string input = "ABC!DE_FG";
    // Replace ! with _ and  _ with %
    var replacements = new Dictionary<char, char?>
                           {
                               {'!', '_'},
                               {'_', '%'}
                           };
    string output = input.ReplaceMultiple(replacements);
    // Output will be ABC_DE%FG and not ABC%DE%FG
}

Comments (2)

mabsterFebruary 16th, 2010 at 03:20

Not bad, Michael. I almost feel, though, that you'd be better off accepting an IDictionary rather than two lists of Char.

That way you have a definite one-to-one mapping of "old" to "new" characters, rather than relying on the two lists "matching up" item for item.

mstumFebruary 16th, 2010 at 03:26

Haha, I had exactly the same thought as I started to all more "rules" and realized that keeping two lists in sync is stupid 🙂