C#idiomatic C#
stringscsharp/fundamentals

Language

C# strings

Learn how strings work in C# — declaration, immutability, literals (regular, verbatim, raw, interpolated), UTF-8 literals, and indexing.

  • page csharp/fundamentals/strings
  • updated 05/08/2026

A string is a sequence of characters. In C#, string is the language keyword for the System.String type. Every string literal you write produces a System.String instance.

string vs. String

The string keyword and the String type name refer to the same type. They compile to identical intermediate language (IL).

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

Prefer the string keyword in your own code. It's consistent with the other built-in type keywords (int, bool, double), and it works without a using System; directive.

Strings are immutable

Immutable means the value can't be changed after it's created. Once you create a string, you can't change its characters. Methods such as ToUpperInvariant, Replace, Substring, and Trim return a new string that contains the modified value. The original instance stays the same.

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

Because strings are immutable, you can safely share them across methods and threads. This immutability explains why the string type behaves like a value type in everyday use even though it's a reference type.

When you build a string from many small pieces in a loop, use System.Text.StringBuilder to append in place and produce a single string at the end:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

String literals

C# offers four literal forms. Each form suits different content. As a quick guide:

  • Use regular literals for short, simple text with at most a few escape sequences.
  • Use verbatim literals when backslashes dominate the content, such as Windows paths or regex patterns.
  • Use raw string literals for multiline or structurally formatted text, such as inline JSON, SQL, XML, or formatted message blocks.
  • Add a $ prefix to any of the aforementioned literals to get an interpolated string when you need to embed values.
  • Add a u8 suffix to a literal to produce a UTF-8 byte sequence (a System.ReadOnlySpan%601 of byte) for byte-oriented APIs. See UTF-8 string literals in the language reference for details.

Regular literals and escape sequences

A regular string literal is enclosed in double quotes. Backslash starts an escape sequence:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

Common escape sequences include \n (newline), \t (tab), \" (literal quote), \\ (literal backslash), \0 (null char), and Unicode escapes (\uXXXX, \UXXXXXXXX).

Beginning in C# 13, \e represents the ESC control character (U+001B). It's the start byte for ANSI terminal escape sequences:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

Use a regular literal when the text is short and contains only a handful of escape sequences. Once the escapes start to outnumber the visible characters, switch to a verbatim or raw literal.

Verbatim literals

A verbatim literal is prefixed with @. Backslashes are treated literally, which is useful for Windows paths and regular-expression patterns:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

To embed a literal quote inside a verbatim string, double it: @"She said ""hi"".". Verbatim strings can also span multiple physical lines.

Verbatim literals are the right choice when backslashes are part of the content but you don't have many embedded quotes. For multiline text or content with quotes, raw string literals are usually clearer.

Raw string literals

For any literal that contains quotes, backslashes, or multiple lines, prefer raw string literals. They eliminate escape noise entirely, which makes them the best fit for inline JSON, SQL, XML, regex patterns, and formatted text blocks where the source should look like the output:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

Raw string literals all but eliminate escape sequences and accommodate any formatting and quoting you need. See Raw string literals for the full rules.

Interpolated strings

A $ prefix turns a literal into an interpolated string. Expressions in {} holes are evaluated and their results inserted, and you can apply standard format specifiers and alignment inside the holes. Interpolation also combines with the other literal forms — use $@"..." to interpolate a verbatim literal, or $"""...""" to interpolate a raw string literal for richly formatted output:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

Interpolation is the recommended way to compose strings from values in everyday code.

Indexing and char

A string is a sequence of UTF-16 code units. The indexer returns one System.Char, which represents a single UTF-16 code unit, not necessarily a complete Unicode code point. Length returns the count of code units.

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

For text that might contain emoji or characters outside the Basic Multilingual Plane, iterate by rune using System.Text.Rune or by grapheme cluster using System.Globalization.StringInfo. Plain char iteration works for most Western text and ASCII-dominant content.

String equality

Equality on string compares the character sequences, not references:

sample · csharpdocs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs

csharp
using System.Text;

namespace StringsOverviewSample;

public static class Program
{
    public static void Main()
    {
        ShowStringKeyword();
        ShowImmutability();
        ShowStringBuilder();
        ShowEscapes();
        ShowEscEscape();
        ShowVerbatim();
        ShowRaw();
        ShowInterpolated();
        ShowUtf8Literal();
        ShowIndexing();
        ShowEqualityIntro();
    }

    private static void ShowStringKeyword()
    {
        // <StringKeyword>
        // The 'string' keyword is an alias for System.String. The two are identical.
        string a = "hello";
        String b = "hello";

        Console.WriteLine(a == b);                // True
        Console.WriteLine(typeof(string) == typeof(String)); // True
        // </StringKeyword>
    }

    private static void ShowImmutability()
    {
        // <Immutability>
        string greeting = "hello";

        // ToUpper returns a *new* string. The original is unchanged.
        string shouted = greeting.ToUpperInvariant();

        Console.WriteLine(greeting);  // hello
        Console.WriteLine(shouted);   // HELLO
        // </Immutability>
    }

    private static void ShowStringBuilder()
    {
        // <StringBuilder>
        // For many sequential edits, StringBuilder avoids allocating a new string each time.
        var builder = new StringBuilder();
        for (int i = 1; i <= 3; i++)
        {
            builder.Append("item ").Append(i).Append(';');
        }
        string result = builder.ToString();
        Console.WriteLine(result);    // item 1;item 2;item 3;
        // </StringBuilder>
    }

    private static void ShowEscapes()
    {
        // <Escapes>
        // Common escape sequences inside a regular string literal.
        string tabbed   = "name:\tAda";          // \t  tab
        string twoLines = "line 1\nline 2";      // \n  newline
        string quoted   = "She said \"hi\".";    // \"  literal quote
        string path     = "C:\\src\\app";        // \\  literal backslash

        Console.WriteLine(tabbed);
        Console.WriteLine(twoLines);
        Console.WriteLine(quoted);
        Console.WriteLine(path);
        // </Escapes>
    }

    private static void ShowEscEscape()
    {
        // <EscEscape>
        // Beginning in C# 13, \e represents the ESC control character (U+001B).
        // It's used to start ANSI terminal escape sequences.
        string esc = "\e[31mError\e[0m: file missing";

        Console.WriteLine(esc);                  // ESC[31mError ESC[0m: file missing
        Console.WriteLine((int)'\e');          // 27
        // </EscEscape>
    }

    private static void ShowVerbatim()
    {
        // <Verbatim>
        // A verbatim string literal (@) treats backslashes literally.
        // Useful for Windows paths and regular expressions.
        string winPath = @"C:\src\app\readme.md";
        string pattern = @"\d{3}-\d{4}";

        Console.WriteLine(winPath);
        Console.WriteLine(pattern);
        // </Verbatim>
    }

    private static void ShowRaw()
    {
        // <Raw>
        // Raw string literals use three or more quotes and need no escaping.
        // The source looks like the output, which is ideal for inline JSON, SQL, XML, and the like.
        string json = """
            {
                "name": "Ada",
                "roles": ["admin", "editor"]
            }
            """;

        string sql = """
            SELECT Id, Name
            FROM   Users
            WHERE  Name = 'O''Brien'
            """;

        Console.WriteLine(json);
        Console.WriteLine(sql);
        // </Raw>
    }

    private static void ShowInterpolated()
    {
        // <Interpolated>
        // The $ prefix evaluates expressions inside { } and inserts their values.
        string name = "Ada";
        int score = 92;

        string greeting = $"Hello, {name}! Your score is {score}.";
        // Format specifiers and alignment work inside the holes.
        string formatted = $"pi = {Math.PI:F3}, padded = |{name,10}|";

        // Combine $ and """ for richly formatted multiline output.
        string report = $"""
            Report for {name}
            -----------------
            Score : {score}
            Grade : {(score >= 90 ? "A" : "B")}
            """;

        Console.WriteLine(greeting);
        Console.WriteLine(formatted);
        Console.WriteLine(report);
        // </Interpolated>
    }

    private static void ShowUtf8Literal()
    {
        // <Utf8Literal>
        // Suffix u8 produces a ReadOnlySpan<byte> of the string's UTF-8 bytes.
        // Common in HTTP, network protocols, and other byte-oriented APIs.
        ReadOnlySpan<byte> verb = "POST"u8;
        ReadOnlySpan<byte> body = """{"status":"ok"}"""u8;

        Console.WriteLine(verb.Length);          // 4
        Console.WriteLine(body.Length);          // 15
        // </Utf8Literal>
    }

    private static void ShowIndexing()
    {
        // <Indexing>
        // A string is a sequence of UTF-16 code units. s[i] returns one char.
        string word = "café";

        Console.WriteLine(word.Length);          // 4
        Console.WriteLine(word[0]);              // c

        foreach (char c in word)
        {
            Console.Write($"{c} ");              // c a f é
        }
        Console.WriteLine();
        // </Indexing>
    }

    private static void ShowEqualityIntro()
    {
        // <EqualityIntro>
        // String equality compares character sequences, not references.
        string left  = "hello";
        string right = string.Concat("hel", "lo");

        Console.WriteLine(left == right);                                      // True
        Console.WriteLine(left.Equals(right, StringComparison.Ordinal));       // True
        // </EqualityIntro>
    }
}

For comparisons that need to be locale-aware or case-aware, pass an explicit System.StringComparison value. Use StringComparison.Ordinal for protocol values, identifiers, and other non-linguistic text.

Common string operations

Use the following table as a quick guide to everyday string operations in C#:

Category What it covers
Search Find characters or substrings, test prefixes and suffixes
Split Break a string into substrings on separators
Concatenate Combine strings — +, interpolation, Concat, Join
Modify Produce a transformed copy — Replace, Trim, Substring
Compare Test equality and ordering with the right StringComparison

The full API surface — every overload, every method — is documented in the System.String reference.

See also