Skip to content

C# Strings β€” Immutability, StringBuilder, Interpolation, and Verbatim Strings

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about C# Strings. We cover key concepts, practical examples, and best practices to help you master this topic.

C# strings are immutable sequences of characters stored as reference types, providing rich manipulation APIs, string interpolation for formatting, verbatim literals for escaping, and StringBuilder for efficient concatenation.

What You'll Learn

You will master strings in C#: understanding string immutability and its performance implications, using string interpolation for readable formatting, verbatim strings for paths and multiline text, StringBuilder for efficient concatenation in loops, and the extensive .NET string API for searching, comparison, and manipulation.

Why It Matters

String operations are among the most common in any application. Poor string handling causes performance problems (excessive allocation), security vulnerabilities (string comparison issues), and maintenance challenges. Understanding string immutability directly impacts memory usage. StringBuilder is essential for high-performance text processing in loops.

Real-World Use

Logging frameworks build large strings efficiently with StringBuilder. JSON serializers use StringBuilder for building output. SQL query builders concatenate fragments efficiently. ASP.NET Core uses StringValues and StringBuilder for HTTP header processing. File processing applications use StringBuilder to assemble output lines.

Learning Path

graph LR
    A["16: Structs"] --> B["17: Strings"]
    B --> C["18: Arrays & Collections"]
    C --> D["19: Generics"]
    D --> E["20: Exception Handling"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style E fill:#4a90d9,stroke:#2c5f8a,color:#fff

String Immutability

string s1 = "Hello";
string s2 = s1;  // Both reference same string

s1 += " World";   // Creates a NEW string, s1 points to new string
// s2 still points to original "Hello"

Console.WriteLine(s1);  // Hello World
Console.WriteLine(s2);  // Hello

Each modification creates a new string object. The original string remains unchanged (immutable).

// The following creates 4 string objects:
string result = "";
for (int i = 0; i < 3; i++)
{
    result += i.ToString();  // "", "0", "01", "012" - 4 allocations!
}

String Literals

// Regular string literal
string regular = "Hello\tWorld\nLine 2";

// Verbatim string (no escape sequences, supports multiline)
string verbatim = @"C:\Users\Alice\Documents\file.txt";
Console.WriteLine(verbatim);  // C:\Users\Alice\Documents\file.txt

// Raw string literal (C# 11) - multiline with embedded quotes
string raw = """
    <element attr="value">
        <child>Content</child>
    </element>
    """;

String Interpolation

string name = "Alice";
int age = 30;
double salary = 75250.50;

// Basic interpolation
string message = $"Name: {name}, Age: {age}";
Console.WriteLine(message);  // Name: Alice, Age: 30

// Format specifiers
string formatted = $"Salary: {salary:C}";      // $75,250.50
string padded = $"Name: {name,-10} Age";       // Right-aligned in 10 chars
string percentage = $"{0.15:P}";               // 15.00%

// Expressions inside interpolation
string expression = $"{name.ToUpper()} is {age + 1} next year";

// Conditional expression
string status = $"{name} is {(age >= 18 ? "adult" : "minor")}";

// Raw string literal with interpolation (C# 11)
string json = $$"""
    {
        "name": "{{name}}",
        "age": {{age}}
    }
    """;

String Comparison

string s1 = "Hello";
string s2 = "hello";

// Default: ordinal (case-sensitive)
Console.WriteLine(s1 == s2);               // False
Console.WriteLine(s1.Equals(s2));           // False

// Case-insensitive
Console.WriteLine(s1.Equals(s2, StringComparison.OrdinalIgnoreCase));  // True

// Culture-sensitive comparison
int result = string.Compare("straße", "strasse", StringComparison.CurrentCulture);
Console.WriteLine($"Compare: {result}");  // 0 (equal in German culture)

// Ordering
string[] names = { "alice", "Bob", "Charlie", "bob" };
Array.Sort(names, StringComparer.OrdinalIgnoreCase);
Console.WriteLine(string.Join(", ", names));  // alice, Bob, bob, Charlie

StringBuilder

using System.Text;

// Efficient string building
var sb = new StringBuilder();
sb.Append("Hello");
sb.Append(' ');
sb.Append("World");
sb.AppendLine("!");
sb.AppendFormat("The count is {0}", 42);
sb.Insert(0, "START: ");
sb.Replace("World", "C#");

string result = sb.ToString();
Console.WriteLine(result);  // START: Hello C#!

Performance Comparison

// Slow: creates 10,001 string objects
string slow = "";
for (int i = 0; i < 10000; i++)
    slow += i.ToString() + ",";

// Fast: single builder, one allocation at end
var sb = new StringBuilder(100000);
for (int i = 0; i < 10000; i++)
{
    sb.Append(i);
    sb.Append(',');
}
string fast = sb.ToString();

String Methods

string text = "  Hello, World! Welcome to C#.  ";

// Trimming
Console.WriteLine(text.Trim());       // "Hello, World! Welcome to C#."
Console.WriteLine(text.TrimStart()); // "Hello, World! Welcome to C#.  "
Console.WriteLine(text.TrimEnd());   // "  Hello, World! Welcome to C#."

// Searching
Console.WriteLine(text.Contains("World"));       // True
Console.WriteLine(text.StartsWith("  Hello"));    // True
Console.WriteLine(text.EndsWith("C#.  "));        // True
Console.WriteLine(text.IndexOf("World"));         // 9
Console.WriteLine(text.LastIndexOf('o'));         // 19

// Extracting
Console.WriteLine(text.Substring(2, 5));          // "Hello"

// Splitting and joining
string csv = "apple,banana,cherry";
string[] fruits = csv.Split(',');
Console.WriteLine(string.Join(" | ", fruits));    // apple | banana | cherry

// Case conversion
Console.WriteLine(text.ToUpper());
Console.WriteLine(text.ToLower());

// Padding
Console.WriteLine("42".PadLeft(5, '0'));  // 00042

// Null/empty checking
Console.WriteLine(string.IsNullOrEmpty(null));  // True
Console.WriteLine(string.IsNullOrWhiteSpace("   "));  // True

String Pooling and Interning

// String literals are interned (same object for same value)
string a = "Hello";
string b = "Hello";
Console.WriteLine(ReferenceEquals(a, b));  // True (same interned object)

// Dynamically created strings are NOT interned
string c = "Hel" + "lo";
Console.WriteLine(ReferenceEquals(a, c));  // True (compile-time constant)

string d = new string("Hello");
Console.WriteLine(ReferenceEquals(a, d));  // False (new object)

// Explicit interning
string e = string.Intern(new string("Hello"));
Console.WriteLine(ReferenceEquals(a, e));  // True (now interned)

Span for Zero-Alloc String Slicing

string fullName = "Alice Smith Johnson";
ReadOnlySpan<char> span = fullName.AsSpan();

// Slice without allocation
var first = span[..5];   // "Alice"
var last = span[6..11];  // "Smith"

Console.WriteLine(first.ToString());  // Alice
Console.WriteLine(last.ToString());   // Smith

Common Mistakes

Mistake 1: Using + in Loops

String concatenation in a loop creates unnecessary garbage. Always use StringBuilder for loop-based string construction.

Mistake 2: Ignoring Culture When Comparing Strings

Using == for string comparison uses ordinal (case-sensitive) comparison. For user-facing text, use StringComparison.InvariantCultureIgnoreCase or similar.

Mistake 3: Not Using string.Empty

// Inconsistent
if (name == "")  // Fine, but:
if (name == string.Empty)  // More consistent

Mistake 4: Excessive Substring Calls

Each Substring allocates a new string. With C# 8+, use ranges and spans for zero-allocation slicing: text[5..10] returns a ReadOnlySpan<char>.

Mistake 5: Using StringBuilder for Simple Concatenations

For 2-3 strings, StringBuilder overhead is unnecessary. Use + or $"" interpolation for simple cases.

Mistake 6: Forgetting That StringBuilder Is Not Thread-Safe

Multiple threads appending to the same StringBuilder instance corrupts its state. Use external synchronization or separate builders.

Practice Questions

  1. What does it mean that strings are immutable? Give an example showing the implications.
  2. When should you use StringBuilder instead of string concatenation?
  3. What is the difference between verbatim strings (@"...") and raw string literals ("""...""")?
  4. Why is culture-sensitive comparison important in string operations?
  5. Write a method that efficiently builds a CSV string from a list of objects using StringBuilder.

Challenge

Write a program that reads a text file, counts word frequencies, and outputs the top 10 words. Use StringBuilder for building the output and demonstrate proper string comparison for case-insensitive word counting.

FAQ

Is `string` a reference type or value type?

String is a reference type. Despite common confusion, strings are stored on the heap and passed by reference. However, their immutability makes them behave like value types in many situations.

What is the maximum length of a string in C#?

The theoretical maximum is about 2 billion characters (Int32.MaxValue). In practice, available memory limits string size. Strings larger than ~1 GB are impractical.

What is string interning?

The CLR maintains a table of unique string literals. When two string literals have the same value, they point to the same interned object. This saves memory but only applies to compile-time constants.

{{< faq "Should I use string.Empty or \"\"?" "Both are equivalent. string.Empty is more readable and avoids creating a new string literal, though the compiler optimizes repeated \"\" to point to the same interned string." >}}

What is the difference between `String` and `string`?

No difference. string is an alias for System.String. Use lowercase string for variable declarations and String for static method calls, following C# conventions.

Mini Project

Create a CSV processing tool:

using System.Text;
using System.Globalization;

public class CsvProcessor
{
    public static string ToCsv<T>(IEnumerable<T> items, params Func<T, object>[] selectors)
    {
        var sb = new StringBuilder(10000);
        bool first = true;

        foreach (var item in items)
        {
            if (!first) sb.AppendLine();
            first = false;

            for (int i = 0; i < selectors.Length; i++)
            {
                if (i > 0) sb.Append(',');
                var value = selectors[i](item);
                var strValue = value?.ToString() ?? "";

                // Escape quotes and wrap in quotes if contains comma or quote
                if (strValue.Contains(',') || strValue.Contains('"'))
                {
                    sb.Append('"');
                    sb.Append(strValue.Replace("\"", "\"\""));
                    sb.Append('"');
                }
                else
                {
                    sb.Append(strValue);
                }
            }
        }

        return sb.ToString();
    }
}

var data = new[]
{
    new { Name = "Alice", Age = 30, Salary = 75000.50m },
    new { Name = "Bob", Age = 25, Salary = 62000.00m },
    new { Name = "Charlie, \"The Great\"", Age = 35, Salary = 85000.75m }
};

string csv = CsvProcessor.ToCsv(data,
    item => item.Name,
    item => item.Age,
    item => item.Salary.ToString("F2", CultureInfo.InvariantCulture)
);

Console.WriteLine("Generated CSV:");
Console.WriteLine(csv);

// Parse back
Console.WriteLine("\nParsed rows:");
var lines = csv.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
    // Simple CSV parser (production code would use a proper parser)
    var fields = line.Split(',');
    Console.WriteLine($"  Name: {fields[0]}, Age: {fields[1]}, Salary: {fields[2]}");
}

Expected output:

Generated CSV:
Alice,30,75000.50
Bob,25,62000.00
"Charlie, ""The Great""",35,85000.75

What's Next

You have mastered strings in C#. The next lesson covers arrays and collections: arrays, List, Dictionary, and HashSet for storing and organizing data.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro