BrandGhost
C# Strings: Complete Guide to String Manipulation in .NET

C# Strings: Complete Guide to String Manipulation in .NET

C# Strings: Complete Guide to String Manipulation in .NET

C# string manipulation is one of the most common tasks you will encounter as a .NET developer. Strings appear in every layer of an application -- from parsing user input and formatting log messages to serializing data and building queries. Understanding how strings work in .NET is not just useful; it is essential for writing correct, performant code.

This guide covers everything you need: from the fundamental mechanics of how strings behave in .NET, to the modern APIs introduced in .NET 6, 7, 8, and 9 that make string handling faster and more expressive than ever. Whether you are just getting started or looking to deepen your knowledge, this guide has you covered.


How Strings Work in .NET

In C#, string is an alias for System.String. The most important property of strings in .NET is immutability -- once a string is created, its content cannot be changed. Every operation that appears to modify a string actually creates a new string object on the heap.

var original = "Hello";
var modified = original + ", World"; // Creates a new string -- original is unchanged
Console.WriteLine(original);  // Hello
Console.WriteLine(modified); // Hello, World

This immutability has significant implications:

  • Multiple references can safely point to the same string without risk of mutation
  • String interning allows the runtime to reuse identical string literals
  • Repeated concatenation in loops creates many short-lived objects, putting pressure on the garbage collector

Understanding immutability helps you make smarter decisions about when to use StringBuilder, Span<char>, or interpolated string handlers -- all covered later in this guide.


String Literals in C#

C# supports several styles of string literals, each suited to different situations. Choosing the right literal style reduces escape noise, improves readability, and can eliminate runtime allocations entirely.

Regular String Literals

The basic form uses double quotes and supports the standard set of C# escape sequences for special characters such as newlines, tabs, and backslashes:

var path = "C:\Users\Nick\Documents";
var newLine = "Line one
Line two";
var tab = "Column1	Column2";

Verbatim String Literals (.NET All Versions)

Verbatim strings (prefixed with @) disable escape processing entirely, so backslashes are treated as literal characters. They are ideal for file paths, Windows registry keys, and embedded multi-line text:

var path = @"C:UsersNickDocuments";
var multiLine = @"Line one
Line two
Line three";

Raw String Literals (.NET 6+)

Raw string literals use triple (or more) double quotes and eliminate nearly all escaping needs. They are perfect for JSON, XML, regular expressions, and any text containing backslashes or double quotes:

var json = """
    {
        "name": "Nick",
        "role": "developer"
    }
    """;

var regex = """^d{3}-d{4}$""";

// Need a literal triple quote inside? Use four quotes to delimit
var withQuotes = """"
    She said """Hello World"""
    """";

Raw string literals respect indentation -- the closing """ sets the left margin, so leading spaces are stripped automatically.

Interpolated Strings

String interpolation with the $ prefix embeds C# expressions directly in the string literal, making it far more readable than string.Format for most day-to-day formatting scenarios:

var name = "Nick";
var count = 42;
var message = $"Hello, {name}! You have {count} messages.";

You can combine @ and $ for verbatim interpolated strings, and you can use raw string literals with interpolation:

var greeting = $"""
    Hello, {name}!
    You have {count} unread messages.
    """;

UTF-8 String Literals (.NET 7+)

The u8 suffix creates a ReadOnlySpan<byte> containing the UTF-8 encoded bytes of the string. This is highly efficient when working with protocols or APIs that require UTF-8 bytes, because no runtime encoding conversion occurs:

ReadOnlySpan<byte> utf8Hello = "Hello, World!"u8;
ReadOnlySpan<byte> contentType = "application/json"u8;

This is a compile-time feature -- the bytes are embedded directly in the assembly.


Essential String Methods

.NET provides a rich set of string methods. Here are the most important ones, with notes on modern alternatives where available.

Searching and Checking

The following methods let you test whether a string contains a substring and locate it by position. For performance-critical paths, prefer the Span<char> overloads to avoid heap allocations:

bool hasQuick = text.Contains("quick"); bool startsWithThe = text.StartsWith("The"); bool endsWithDog = text.EndsWith("dog");

int index = text.IndexOf("fox"); // 16 int lastIndex = text.LastIndexOf("o"); // 41

// Span-based zero-allocation check (.NET 5+) bool containsSpan = text.AsSpan().Contains("fox".AsSpan(), StringComparison.Ordinal);


### Modifying Strings

Because strings are immutable, all modification methods return a new string instance. The most useful operations are trimming whitespace, changing case, replacing substrings, and slicing with the range operator:



var trimmed = original.Trim();                     // "Hello, World!"
var upper = original.Trim().ToUpperInvariant();    // "HELLO, WORLD!"
var lower = original.Trim().ToLowerInvariant();    // "hello, world!"
var replaced = original.Replace("World", "Nick"); // "  Hello, Nick!  "

// Remove by range (.NET 5+)
var sliced = "Hello, World!"[7..]; // "World!" -- range operator

Splitting and Joining

Splitting a string into an array and joining an array back into a string are two of the most common string operations in everyday .NET development. The .NET 5+ StringSplitOptions.TrimEntries flag eliminates the need for a subsequent Select(s => s.Trim()) call:

string[] parts = csv.Split(',');

// Remove empty entries and trim whitespace string[] cleaned = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

// Join back var rejoined = string.Join(", ", parts); // "alpha, beta, gamma, delta"

// Join a span (.NET 6+) var joined = string.Join('-', parts.AsSpan(0, 2)); // "alpha-beta"


### Formatting

C# provides several ways to format numbers, dates, and other values into human-readable strings. Interpolation is the modern default, but composite formatting with `string.Format` remains useful in localization and logging scenarios:


var formatted = pi.ToString("F4");        // "3.1416"
var currency = 1234.5m.ToString("C2");   // "$1,234.50"

// Composite formatting
var message = string.Format("Value: {0:F2}", pi);

// Interpolation (preferred)
var modern = $"Value: {pi:F4}";

String Comparison Best Practices

String comparison is a common source of bugs, especially in cross-platform and internationalized applications. The key rule: always pass an explicit StringComparison value.

var a = "Hello";
var b = "hello";

// ✅ Correct -- ordinal, culture-independent
bool equal = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);

// ❌ Avoid -- allocates new strings
bool bad = a.ToLower() == b.ToLower();

// ✅ For sorting and ordered collections
var comparer = StringComparer.OrdinalIgnoreCase;
var sorted = new SortedDictionary<string, int>(comparer);

StringComparison.OrdinalIgnoreCase is the correct default for most non-linguistic comparisons such as file paths, command names, configuration keys, and identifiers. Reserve CurrentCulture or InvariantCulture for user-facing text that must follow locale conventions.


StringBuilder: Efficient String Building

When you need to build a string through many concatenation steps -- especially in a loop -- StringBuilder is your friend. Unlike repeated + concatenation, StringBuilder maintains an internal buffer and avoids creating intermediate strings:

using System.Text;

// ❌ Slow for large N -- creates N intermediate strings
var bad = "";
for (var i = 0; i < 1000; i++)
{
    bad += i.ToString();
}

// ✅ Efficient -- reuses internal buffer
var sb = new StringBuilder(capacity: 4096);
for (var i = 0; i < 1000; i++)
{
    sb.Append(i);
}
var result = sb.ToString();

For more detail on when to choose StringBuilder over concatenation, and how .NET 6 interpolated string handlers change the picture, see the spoke article in this cluster.


Span and ReadOnlySpan

Span<char> and ReadOnlySpan<char> are stack-allocated slice types that let you work with substrings without heap allocation. They are powerful tools for high-performance string processing:

var line = "2026-05-01T21:00:00Z";

// No allocation -- just a view into the original string
ReadOnlySpan<char> datePart = line.AsSpan(0, 10);  // "2026-05-01"
ReadOnlySpan<char> timePart = line.AsSpan(11, 8);  // "21:00:00"

// Parse directly from span -- no intermediate string
bool parsed = int.TryParse(datePart[0..4], out int year);
Console.WriteLine(year); // 2026

string.Create() takes this further, allowing you to write into a fresh string without intermediate allocations:

var id = 42;
var name = "Nick";
var key = string.Create(name.Length + 10, (id, name), static (span, state) =>
{
    var (id, name) = state;
    name.AsSpan().CopyTo(span);
    span[name.Length] = '-';
    id.TryFormat(span[(name.Length + 1)..], out _);
});
Console.WriteLine(key); // Nick-42

SearchValues (.NET 8+)

For searching strings for any character from a set, .NET 8 introduced SearchValues<T>. It uses SIMD vectorization to search extremely fast -- much faster than iterating manually or using IndexOfAny with a char array:

using System.Buffers;

// Create once, reuse many times
private static readonly SearchValues<char> _delimiters =
    SearchValues.Create(",;|	");

public static int FindFirstDelimiter(ReadOnlySpan<char> text)
{
    return text.IndexOfAny(_delimiters);
}

SearchValues is most valuable in hot paths -- parsers, tokenizers, log processors -- where you scan large amounts of text for delimiter characters. Building the SearchValues object has a one-time cost, so declare it as a static readonly field.


Modern .NET String APIs at a Glance

The table below summarizes the most impactful new string APIs introduced in recent .NET releases. Knowing which version each feature requires helps you decide what is available in your target framework:

|---------|---------|-------------| | Raw string literals """...""" | .NET 6 / C# 11 | No-escape multi-line strings | | UTF-8 string literals "..."u8 | .NET 7 | ReadOnlySpan<byte> at compile time | | SearchValues<char> | .NET 8 | Vectorized multi-char search | | Interpolated string handlers | .NET 6 | Zero-allocation interpolation in hot paths | | string.Create() | .NET 5 | Allocation-free string construction | | Span<char> integration | .NET Core 2.1+ | Zero-allocation substring slicing | | TrimEntries on Split | .NET 5 | Trim whitespace during split |


Practical Example: Parsing a CSV Line

Here is a practical example that combines several techniques -- Span<char> slicing, SearchValues, and Split with options -- to parse a CSV line efficiently:

using System.Buffers;

namespace StringDemo;

public static class CsvParser
{
    private static readonly SearchValues<char> _specialChars =
        SearchValues.Create(","");

    public static IReadOnlyList<string> ParseLine(string line)
    {
        // Fast path: no special characters
        if (line.AsSpan().IndexOfAny(_specialChars) < 0)
        {
            return line.Split(',');
        }

        // Slow path: quoted fields
        var fields = new List<string>();
        var span = line.AsSpan();

        while (!span.IsEmpty)
        {
            if (span[0] == '"')
            {
                span = span[1..]; // skip opening quote
                var end = span.IndexOf('"');
                fields.Add(end < 0 ? span.ToString() : span[..end].ToString());
                if (end >= 0)
                {
                    span = span[(end + 1)..];
                }
                else
                {
                    break;
                }
            }
            else
            {
                var comma = span.IndexOf(',');
                if (comma < 0)
                {
                    fields.Add(span.ToString());
                    break;
                }
                fields.Add(span[..comma].ToString());
                span = span[(comma + 1)..];
            }

            if (!span.IsEmpty && span[0] == ',')
            {
                span = span[1..];
            }
        }

        return fields;
    }
}

This kind of approach -- combining modern APIs with classic algorithms -- represents the practical sweet spot for real-world .NET code.


Strings in the Broader .NET Ecosystem

Strings interact with many other parts of .NET development. When working with plugin architectures in C#, you often parse string-based configuration or command names to route logic. When implementing feature slicing in C#, strings flow through commands, queries, and handler boundaries as keys and identifiers.

Even design patterns interact with strings. The decorator design pattern in C# is commonly used to add logging or formatting to services -- both tasks that involve string manipulation. When working with AI features like building a semantic search engine with Semantic Kernel in C#, strings are the primary medium for queries, embeddings, and responses.

Understanding C# enums also intersects with strings -- the C# Enum to String conversion guide shows how enum values become string representations efficiently using modern .NET APIs.


Performance Summary

This table provides a quick reference for matching the right string approach to the task at hand. Choosing the correct tool from the start avoids unnecessary allocations and keeps hot paths fast:

|----------|---------------------| | Simple one-time concatenation | + operator or interpolation | | Building in a loop | StringBuilder | | Parsing substrings | ReadOnlySpan<char> + AsSpan() | | Multi-char searching | SearchValues<char> (.NET 8+) | | UTF-8 protocol bytes | "..."u8 literal (.NET 7+) | | Building new strings | string.Create() | | Case-insensitive comparison | StringComparison.OrdinalIgnoreCase |


FAQ

Here are the most common questions developers ask when learning C# strings and string manipulation in .NET.

They are identical. string is a C# keyword that is an alias for System.String. Use string in code (it is idiomatic C#) and String only when calling static methods like String.IsNullOrEmpty -- though many developers use the lowercase keyword form even there.

Why are C# strings immutable?

Immutability makes strings safe to share across threads, enables string interning, and simplifies reasoning about data. The trade-off is that modification-heavy workflows must use StringBuilder or Span<char> to avoid excessive allocations.

When should I use StringBuilder vs string interpolation?

Use string interpolation ($"...") for simple, one-off formatting. It is readable and .NET 6+ compilers optimize it with interpolated string handlers. Use StringBuilder when building strings iteratively in a loop or across multiple conditional steps.

What is StringComparison.OrdinalIgnoreCase and when should I use it?

OrdinalIgnoreCase compares strings byte-by-byte after converting to uppercase using invariant casing rules. It is fast, culture-independent, and the correct choice for comparing identifiers, file names, URLs, configuration keys, and any non-linguistic string. Avoid ToLower() comparisons -- they allocate and can produce incorrect results in some cultures.

What are raw string literals in C#?

Raw string literals (C# 11 / .NET 6+) use triple or more double quotes ("""...""") and do not require escape sequences for backslashes or double quotes inside the string. They are ideal for embedded JSON, XML, SQL, and regular expressions.

What is the u8 suffix in C# strings?

The u8 suffix (introduced in .NET 7) creates a ReadOnlySpan<byte> containing the UTF-8 encoded bytes of the string literal at compile time. It is zero-cost at runtime and useful when working with network protocols, file I/O, or any API that expects UTF-8 bytes.

How does SearchValues improve string searching performance?

SearchValues<char> (introduced in .NET 8) pre-computes SIMD-optimized lookup tables for a set of characters. Calling IndexOfAny with a SearchValues instance is significantly faster than using a char array, especially on long strings. Declare it as a static readonly field to pay the one-time construction cost only once.

What Is String Interpolation In C# - What You Need To Know

What is string interpolation in C#? Learn about its definition, syntax, and benefits for improving code readability and efficiency. Check out this guide!

How to Compare Strings in CSharp: Tips and Tricks You Need to Know

Wondering how to compare strings in CSharp? We'll compare using string.Equals(), string.Compare(), and == operator to weigh the pros and the cons.

C# Multiline Strings: Verbatim Literals, Raw String Literals, and UTF-8 Literals

Learn every C# multiline string technique: verbatim @-strings, C# 11 raw string literals with triple quotes, and .NET 7 UTF-8 u8 literals with code examples.

An error has occurred. This application may no longer respond until reloaded. Reload