C#
string manipulation
character replacement
programming
code tutorial

Replace multiple characters in a C string

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, strings are immutable, so every replacement produces a new value rather than modifying the existing one in place. That matters when you need to replace several different characters, because the cleanest approach depends on whether you have a short fixed set of replacements or a larger mapping. The good news is that C# gives you straightforward options for both cases.

Replacing a few characters with chained Replace

If the set of characters is small and fixed, repeated calls to Replace are perfectly readable.

csharp
1using System;
2
3class Program
4{
5    static void Main()
6    {
7        string input = "2025/03/07 report-final.txt";
8        string normalized = input
9            .Replace('/', '-')
10            .Replace(' ', '_')
11            .Replace('.', '_');
12
13        Console.WriteLine(normalized);
14    }
15}

This style is easy to maintain when you only have a few substitutions. It also makes the transformation order explicit. That order matters whenever one replacement can affect the next one. For example, replacing 'a' with 'b' and then replacing 'b' with 'c' does not produce the same result as doing those operations in the opposite order.

For pure character-to-character cleanup, the chained approach is often enough. If performance is not a bottleneck and the mapping is short, it is usually the most readable answer.

Replacing many characters in one pass

When the mapping grows, repeated string allocations become noisy and less efficient. In that case, walk the string once and build the result with StringBuilder.

csharp
1using System;
2using System.Collections.Generic;
3using System.Text;
4
5class Program
6{
7    static void Main()
8    {
9        string input = "name/email:[email protected]";
10        var map = new Dictionary<char, char>
11        {
12            ['@'] = '_',
13            ['/'] = '_',
14            [':'] = '_',
15            ['.'] = '_'
16        };
17
18        var builder = new StringBuilder(input.Length);
19
20        foreach (char ch in input)
21        {
22            builder.Append(map.TryGetValue(ch, out char replacement) ? replacement : ch);
23        }
24
25        Console.WriteLine(builder.ToString());
26    }
27}

This version does a single pass over the input, which scales better for long strings or large replacement maps. It is also easier to extend because the replacements live in one lookup table instead of a long chain of method calls.

Using regular expressions for pattern-based cleanup

Sometimes the requirement is not "replace these exact four characters" but "replace anything that is not a letter or digit." That is a pattern problem, and Regex.Replace is usually the right tool.

csharp
1using System;
2using System.Text.RegularExpressions;
3
4class Program
5{
6    static void Main()
7    {
8        string input = "Quarter #1: revenue growth!";
9        string slug = Regex.Replace(input, @"[^A-Za-z0-9]+", "-").Trim('-');
10        Console.WriteLine(slug.ToLowerInvariant());
11    }
12}

A regex is more expressive than a character map, but it is also heavier and easier to misuse. Reach for it when the rule is truly pattern-based rather than a simple set of individual substitutions.

Choosing the right approach

A useful rule of thumb is:

  • Use chained Replace for a small, fixed list.
  • Use StringBuilder plus a lookup map for many character swaps.
  • Use Regex.Replace when the rule is defined by a pattern.

These choices are not only about speed. They are also about clarity. A future reader should be able to see whether the code is performing literal substitutions or applying a broader normalization rule.

Common Pitfalls

The first pitfall is expecting the original string to change. It never does. Always capture the returned value from Replace, Regex.Replace, or ToString.

Another issue is replacement order. Chained replacements happen sequentially, so one pass can affect the next. That is fine when deliberate, but it can surprise you if you expect all replacements to happen simultaneously.

Developers also overuse regex for simple cases. A regex can solve the problem, but it makes the code harder to read and may add unnecessary overhead when a few Replace calls would be enough.

Finally, be careful with character replacement versus string replacement. Replacing characters is straightforward because each input character maps to one output character. Replacing substrings can create overlapping cases that require different logic.

Summary

  • Strings in C# are immutable, so every replacement returns a new string.
  • Chained Replace calls are best for a short and fixed set of characters.
  • A StringBuilder and lookup map work well for larger replacement sets.
  • 'Regex.Replace is useful when the rule is pattern-based instead of literal.'
  • Watch replacement order and always assign the returned string.

Course illustration
Course illustration