From ae9c85195554a133f3d838678ea4c7cf43ad3183 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 May 2026 16:42:47 -0400 Subject: [PATCH 1/5] First draft --- docs/csharp/fundamentals/strings/index.md | 117 ++++++++++++++ docs/csharp/fundamentals/strings/nameof.md | 86 ++++++++++ .../strings/raw-string-literals.md | 81 ++++++++++ .../strings/snippets/nameof/Program.cs | 153 ++++++++++++++++++ .../strings/snippets/nameof/nameof.csproj | 8 + .../snippets/raw-string-literals/Program.cs | 127 +++++++++++++++ .../raw-string-literals.csproj | 8 + .../snippets/strings-overview/Program.cs | 141 ++++++++++++++++ .../strings-overview/strings-overview.csproj | 8 + docs/csharp/toc.yml | 8 + 10 files changed, 737 insertions(+) create mode 100644 docs/csharp/fundamentals/strings/index.md create mode 100644 docs/csharp/fundamentals/strings/nameof.md create mode 100644 docs/csharp/fundamentals/strings/raw-string-literals.md create mode 100644 docs/csharp/fundamentals/strings/snippets/nameof/Program.cs create mode 100644 docs/csharp/fundamentals/strings/snippets/nameof/nameof.csproj create mode 100644 docs/csharp/fundamentals/strings/snippets/raw-string-literals/Program.cs create mode 100644 docs/csharp/fundamentals/strings/snippets/raw-string-literals/raw-string-literals.csproj create mode 100644 docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs create mode 100644 docs/csharp/fundamentals/strings/snippets/strings-overview/strings-overview.csproj diff --git a/docs/csharp/fundamentals/strings/index.md b/docs/csharp/fundamentals/strings/index.md new file mode 100644 index 0000000000000..d7112e8e9ae95 --- /dev/null +++ b/docs/csharp/fundamentals/strings/index.md @@ -0,0 +1,117 @@ +--- +title: "Strings in C#" +description: Learn how strings work in C# — declaration, immutability, literals (regular, verbatim, raw, interpolated), UTF-8 literals, and indexing. +ms.date: 05/08/2026 +ms.topic: overview +ai-usage: ai-assisted +--- + +# C# strings + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. +> +> **Coming from Java or C++?** A C# `string` is an immutable reference type backed by . UTF-16 is the in-memory encoding, similar to Java's `String`. Unlike C/C++, strings aren't null-terminated and don't decay into pointers. + +A *string* is a sequence of characters. In C#, `string` is the language keyword for the 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 IL. + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="StringKeyword"::: + +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. Reserve `String` for places where you need to call static members and want the type name visible (for example, `String.IsNullOrEmpty(value)` reads slightly more clearly than `string.IsNullOrEmpty(value)` to some teams — both compile identically). + +## Strings are immutable + +Once a `string` is created, its characters never change. Methods such as `ToUpperInvariant`, `Replace`, `Substring`, and `Trim` return a *new* string. The original instance is unchanged. + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Immutability"::: + +Immutability makes strings safe to share across methods and threads, and it's why the `string` type behaves like a value in everyday use even though it's a reference type. + +When you build a string from many small pieces in a loop, each `+` or `Concat` call allocates a new instance. For that scenario, use , which appends in place and produces a single string at the end: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="StringBuilder"::: + +For a small fixed number of pieces, plain interpolation or `string.Concat` is clearer and just as fast. + +## String literals + +C# offers four literal forms. Each is suited to different content. + +### Regular literals and escape sequences + +A regular string literal is enclosed in double quotes. Backslash starts an escape sequence: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Escapes"::: + +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: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="EscEscape"::: + +### Verbatim literals + +A verbatim literal is prefixed with `@`. Backslashes are treated literally, which is useful for Windows paths and regular-expression patterns: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Verbatim"::: + +To embed a literal quote inside a verbatim string, double it: `@"She said ""hi""."`. Verbatim strings can also span multiple physical lines. + +### Raw string literals + +For any literal that contains quotes, backslashes, or multiple lines, prefer **raw string literals**. They eliminate escape noise entirely. See [Raw string literals](raw-string-literals.md) for the full rules. + +### Interpolated strings + +A `$` prefix turns a literal into an *interpolated string*. Expressions in `{}` holes are evaluated and their results inserted: `$"hello, {name}"`. Interpolation is the recommended way to compose strings from values in everyday code, and it works with raw string literals too — `$"""..."""`. + +## UTF-8 string literals + +A `u8` suffix produces a of UTF-8 bytes instead of a `string`. UTF-8 literals are useful for HTTP, network protocols, file formats, and other byte-oriented APIs that don't need a UTF-16 `string` at all: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Utf8Literal"::: + +The bytes are emitted at compile time, so there's no runtime encoding cost. + +## Indexing and `char` + +A `string` is a sequence of UTF-16 *code units*. The indexer returns one , which represents a single UTF-16 code unit, not necessarily a complete Unicode code point. `Length` returns the count of code units. + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Indexing"::: + +For text that may contain emoji or characters outside the Basic Multilingual Plane, iterate by *rune* using or by grapheme cluster using . Plain `char` iteration works for most Western text and ASCII-dominant content. + +## String equality + +Equality on `string` compares the character sequences, not references: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="EqualityIntro"::: + +For comparisons that need to be locale- or case-aware, pass an explicit value. Use `StringComparison.Ordinal` for protocol values, identifiers, and other non-linguistic text. + +## Common string operations + +The articles in this section cover the everyday operations on strings: + +| 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 reference. + +## See also + +- [Raw string literals](raw-string-literals.md) +- [`nameof` operator](nameof.md) +- +- +- +- diff --git a/docs/csharp/fundamentals/strings/nameof.md b/docs/csharp/fundamentals/strings/nameof.md new file mode 100644 index 0000000000000..d51db85e41958 --- /dev/null +++ b/docs/csharp/fundamentals/strings/nameof.md @@ -0,0 +1,86 @@ +--- +title: "The nameof operator in C#" +description: Use the nameof operator to capture an identifier as a compile-time string for argument validation, change notifications, attributes, and logging. +ms.date: 05/08/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# The `nameof` operator + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete operator reference, see [`nameof`](../../language-reference/operators/nameof.md) in the language reference. + +The `nameof` operator returns the textual identifier of a symbol — a variable, parameter, type, member, or namespace — as a compile-time `string` constant. Anywhere you'd otherwise hardcode an identifier as a string, use `nameof`: the compiler verifies that the symbol exists, and rename refactorings update the result automatically. + +## What `nameof` returns + +`nameof` evaluates to the *final* identifier in its operand. It runs at compile time and has no runtime cost. + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="Basic"::: + +`nameof(customer.Name)` returns `"Name"`, not `"customer.Name"`. For the full qualified expression, only the last identifier is captured. + +## Argument validation + +The classic use is producing the parameter name in a thrown exception. Pass `nameof(parameter)` instead of the literal string `"parameter"` so a future rename can't leave the message lying: + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="GuardClause"::: + +For null checks specifically, prefer the framework's helpers. captures the argument's name automatically through , so a separate `nameof` isn't needed: + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="ThrowIfNull"::: + +Use `nameof` for the cases the helpers don't cover: , (when validating something other than a single argument), and other guard messages. + +## Property change notifications + +Types that implement raise an event whose payload includes the changed property's name. Hardcoding the name as a string creates a silent bug if the property is renamed and the string isn't. Use `nameof`: + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="PersonType"::: + +The setter calls `OnPropertyChanged(nameof(Name))` so the property name and the change notification stay in sync. Run the example to see the events fire: + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="PropertyChanged"::: + +## `nameof` in attribute arguments + +`nameof` is valid inside attribute arguments. The compiler resolves identifiers in the surrounding scope, including the parameters of the method the attribute targets. This is the idiomatic way to refer to a parameter from an attribute such as : + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="AttributeUsage"::: + +If the parameter is renamed, the `nameof` argument is updated by the same refactoring — the attribute can't fall out of date. + +## Unbound generic types + +Beginning in C# 14, `nameof` accepts an *unbound* generic type. The result is the simple type name without any arity or type-argument list: + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="UnboundGenerics"::: + +This is useful in logging, diagnostic messages, and attribute arguments where the generic type name matters but the type arguments don't. + +## Qualified names + +For a qualified expression — a type with a namespace, or a member with a receiver — `nameof` returns only the *last* identifier: + +:::code language="csharp" source="snippets/nameof/Program.cs" ID="QualifiedName"::: + +If you need the fully qualified name, use on a `Type` instance. `nameof` is for identifiers, not paths. + +## Prefer `nameof` to identifier strings + +Anywhere a method, property, parameter, type, or namespace is referred to by name in code, use `nameof` instead of a string literal. Compared to a hardcoded string: + +- The compiler verifies that the symbol exists. A typo becomes a build error, not a silent runtime bug. +- Rename refactorings update the result automatically. Hardcoded strings drift out of sync. +- The result is a compile-time constant, so there's no runtime cost. + +The recommendation applies to logging messages, exception arguments, attribute arguments, property-change notifications, and serialization key constants tied to a member name. + +## See also + +- [Strings overview](index.md) +- [Raw string literals](raw-string-literals.md) +- [`nameof` operator (language reference)](../../language-reference/operators/nameof.md) +- +- diff --git a/docs/csharp/fundamentals/strings/raw-string-literals.md b/docs/csharp/fundamentals/strings/raw-string-literals.md new file mode 100644 index 0000000000000..7138a42cc913b --- /dev/null +++ b/docs/csharp/fundamentals/strings/raw-string-literals.md @@ -0,0 +1,81 @@ +--- +title: "Raw string literals in C#" +description: Use raw string literals to write strings that contain quotes, backslashes, or multiple lines without escape sequences. +ms.date: 05/08/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Raw string literals + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete grammar, see the [language reference](../../language-reference/tokens/raw-string.md). + +A *raw string literal* is delimited by three or more double quotes. Inside the delimiters, every character is taken literally — quotes and backslashes don't need escaping, and newlines are preserved as written. Use raw strings for any string that contains quotes, backslashes, or multiple lines: JSON, XML, SQL, regular expressions, file paths, and code samples. + +## A literal that contains quotes and backslashes + +A regular literal needs escapes for `"` and `\`. A verbatim literal still needs `""` to embed a quote. A raw literal needs neither: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="EscapeContrast"::: + +Each form produces the same string, but the raw version reads exactly like the JSON it represents. + +## Single-line raw strings + +The opening and closing delimiters are each at least three double quotes. The content sits between them on the same line. Quotes and backslashes inside the content are literal: + +:::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="SingleLine"::: + +A single-line raw string can't be empty between its delimiters, and it can't begin or end with a quote unless the delimiter has more quotes than the content. + +## Multiline raw strings + +For multiline content, the opening `"""` ends the line and the closing `"""` starts its own. Everything between the two delimiters is the value of the string, exactly as written: + +:::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="Multiline"::: + +The newline immediately after the opening `"""` and the newline immediately before the closing `"""` are not part of the value. They're delimiter whitespace. + +## Indentation: the closing delimiter sets the margin + +The column of the closing `"""` defines a left margin. Whitespace up to that column is stripped from every content line. This rule lets you indent the literal to match the surrounding code without polluting the value: + +:::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="Indentation"::: + +If a content line has fewer leading whitespace characters than the closing delimiter's column, the compiler reports an error. Keep all content lines indented at least as much as the closing `"""`. + +## Content that contains `"""` + +When the content itself contains a run of three or more quotes, increase the delimiter count. Use four quotes — or more — at the opening and closing. The delimiter count just has to exceed the longest run of quotes anywhere in the content: + +:::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="MoreQuotes"::: + +You can scale this up to any number of quotes. Five-quote and six-quote delimiters work the same way. + +## Raw interpolated strings + +A `$` prefix on a raw string enables interpolation. Expressions in `{}` holes are evaluated and their results inserted into the value: + +:::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="RawInterpolated"::: + +If the content also needs literal `{` or `}` characters — common when generating JSON, CSS, or code — add more `$` signs. Each `$` raises the number of braces required to mark an interpolation hole. With `$$`, single `{` and `}` are literal and only `{{...}}` is treated as a hole: + +:::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="DoubleDollarInterpolation"::: + +You can use any number of `$` signs to disambiguate brace runs. Triple-`$` literals are rare but available. + +## When to choose which literal + +Use a **raw string literal** whenever the content contains quotes, backslashes, or multiple lines. The result is shorter to read, easier to paste in or out of, and free of escape-sequence bugs. + +Use a **regular string literal** for short, single-line values without quotes or backslashes — names, messages, format placeholders. + +Use a **verbatim string literal** (`@"..."`) only when working with existing code that uses them. For new code, raw strings cover every case verbatim strings cover, with cleaner syntax for embedded quotes. + +## See also + +- [Strings overview](index.md) +- [`nameof` operator](nameof.md) +- [Raw string literals (language reference)](../../language-reference/tokens/raw-string.md) +- diff --git a/docs/csharp/fundamentals/strings/snippets/nameof/Program.cs b/docs/csharp/fundamentals/strings/snippets/nameof/Program.cs new file mode 100644 index 0000000000000..f1c4f39d4fadf --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/nameof/Program.cs @@ -0,0 +1,153 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace NameofSample; + +public static class Program +{ + public static void Main() + { + ShowBasic(); + ShowGuardClause(); + ShowThrowIfNull(); + ShowPropertyChanged(); + ShowAttributeUsage(); + ShowUnboundGenerics(); + ShowQualifiedName(); + } + + private static void ShowBasic() + { + // + // nameof produces the textual identifier of a symbol at compile time. + Console.WriteLine(nameof(Customer)); // Customer + Console.WriteLine(nameof(Customer.Name)); // Name + + var customer = new Customer("Ada"); + Console.WriteLine(nameof(customer)); // customer + Console.WriteLine(nameof(customer.Name)); // Name + // + } + + private static void ShowGuardClause() + { + // + try + { + Greet(""); + } + catch (ArgumentException ex) + { + // The exception's ParamName is the literal "name", produced by nameof at compile time. + Console.WriteLine($"{ex.ParamName}: {ex.Message.Split(' ')[0]}"); + } + + static void Greet(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Name must be non-empty.", nameof(name)); + } + Console.WriteLine($"Hello, {name}!"); + } + // + } + + private static void ShowThrowIfNull() + { + // + // ArgumentNullException.ThrowIfNull captures the argument's name automatically + // through [CallerArgumentExpression], so a separate nameof isn't required for + // the null check. Use nameof for cases the helpers don't cover. + Customer? maybeCustomer = null; + + try + { + Save(maybeCustomer); + } + catch (ArgumentNullException ex) + { + Console.WriteLine(ex.ParamName); // maybeCustomer + } + + static void Save(Customer? customer) + { + ArgumentNullException.ThrowIfNull(customer); + // ... + } + // + } + + private static void ShowPropertyChanged() + { + // + var person = new Person(); + person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}"); + + person.Name = "Ada"; // changed: Name + person.Name = "Grace"; // changed: Name + // + } + + private static void ShowAttributeUsage() + { + // + // nameof works inside attribute arguments. The compiler resolves the + // identifier even when the attribute targets a method or its parameters. + Console.WriteLine(NormalizeOrNull(" hi ") ?? ""); // hi + Console.WriteLine(NormalizeOrNull(null) ?? ""); // + + [return: NotNullIfNotNull(nameof(input))] + static string? NormalizeOrNull(string? input) => input?.Trim(); + // + } + + private static void ShowUnboundGenerics() + { + // + // Beginning in C# 14, nameof accepts an unbound generic type. + // The result is the simple type name without any type-argument list. + Console.WriteLine(nameof(List<>)); // List + Console.WriteLine(nameof(Dictionary<,>)); // Dictionary + // + } + + private static void ShowQualifiedName() + { + // + // For a qualified expression, nameof returns only the final identifier. + Console.WriteLine(nameof(System.Collections.Generic.List)); // List + Console.WriteLine(nameof(Customer.Name)); // Name + // + } +} + +// +public sealed record Customer(string Name); +// + +// +public sealed class Person : INotifyPropertyChanged +{ + private string _name = ""; + + public string Name + { + get => _name; + set + { + if (_name == value) return; + _name = value; + // nameof keeps the property name and the change notification in sync. + // Renaming the property automatically updates this argument. + OnPropertyChanged(nameof(Name)); + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); +} +// diff --git a/docs/csharp/fundamentals/strings/snippets/nameof/nameof.csproj b/docs/csharp/fundamentals/strings/snippets/nameof/nameof.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/nameof/nameof.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/fundamentals/strings/snippets/raw-string-literals/Program.cs b/docs/csharp/fundamentals/strings/snippets/raw-string-literals/Program.cs new file mode 100644 index 0000000000000..97500c11eec1c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/raw-string-literals/Program.cs @@ -0,0 +1,127 @@ +namespace RawStringLiteralsSample; + +public static class Program +{ + public static void Main() + { + ShowSingleLine(); + ShowEscapeContrast(); + ShowMultiline(); + ShowIndentation(); + ShowMoreQuotes(); + ShowRawInterpolated(); + ShowDoubleDollarInterpolation(); + } + + private static void ShowSingleLine() + { + // + // A raw string literal starts and ends with at least three quotes. + // Inside, " and \ are literal — no escaping required. + string message = """She said "hi" and left."""; + string regex = """\d{3}-\d{4}"""; + + Console.WriteLine(message); // She said "hi" and left. + Console.WriteLine(regex); // \d{3}-\d{4} + // + } + + private static void ShowEscapeContrast() + { + // + // Same JSON value, three ways: + string regular = "{ \"name\": \"Ada\", \"path\": \"C:\\\\src\" }"; + string verbatim = @"{ ""name"": ""Ada"", ""path"": ""C:\\src"" }"; + string raw = """{ "name": "Ada", "path": "C:\\src" }"""; + + Console.WriteLine(regular == raw); // True + Console.WriteLine(verbatim == raw); // True + // + } + + private static void ShowMultiline() + { + // + // The opening """ and closing """ each sit on their own line. + // The content between them is the value, exactly as written. + string sql = """ + SELECT id, name + FROM customers + WHERE active = 1 + """; + + Console.WriteLine(sql); + // + } + + private static void ShowIndentation() + { + // + // The column of the closing """ sets a left margin. + // Whitespace up to that column is stripped from every content line. + string xml = """ + + book + + """; + + // First content line begins at column 0 of the value: + Console.WriteLine(xml); + // + // book + // + // + } + + private static void ShowMoreQuotes() + { + // + // When the content itself contains """, use four (or more) quotes + // for the delimiters. The delimiter count just has to exceed any + // run of quotes in the content. + string sample = """" + The token """ marks a raw string literal. + """"; + + Console.WriteLine(sample); + // + } + + private static void ShowRawInterpolated() + { + // + // A single $ before """ enables interpolation: single { and } mark a hole. + // Inside a single-$ raw string, literal braces aren't allowed — use $$ when + // the content also contains literal { or }. + string name = "Ada"; + int score = 95; + + string report = $""" + Player: {name} + Score: {score} + Updated: {DateTime.UtcNow:yyyy-MM-dd} + """; + + Console.WriteLine(report); + // + } + + private static void ShowDoubleDollarInterpolation() + { + // + // Each $ raises the brace count for an interpolation hole. + // With $$, single { and } are literal; only {{ and }} mark a hole. + // Useful when the content has lots of literal braces (JSON, CSS, code). + string user = "alice"; + + string template = $$""" + { + "filter": { "user": "{{user}}" }, + "limit": 10 + } + """; + + Console.WriteLine(template); + // + } +} diff --git a/docs/csharp/fundamentals/strings/snippets/raw-string-literals/raw-string-literals.csproj b/docs/csharp/fundamentals/strings/snippets/raw-string-literals/raw-string-literals.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/raw-string-literals/raw-string-literals.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs b/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs new file mode 100644 index 0000000000000..548bcf9b18cb4 --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs @@ -0,0 +1,141 @@ +using System.Text; + +namespace StringsOverviewSample; + +public static class Program +{ + public static void Main() + { + ShowStringKeyword(); + ShowImmutability(); + ShowStringBuilder(); + ShowEscapes(); + ShowEscEscape(); + ShowVerbatim(); + ShowUtf8Literal(); + ShowIndexing(); + ShowEqualityIntro(); + } + + private static void ShowStringKeyword() + { + // + // 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 + // + } + + private static void ShowImmutability() + { + // + string greeting = "hello"; + + // ToUpper returns a *new* string. The original is unchanged. + string shouted = greeting.ToUpperInvariant(); + + Console.WriteLine(greeting); // hello + Console.WriteLine(shouted); // HELLO + // + } + + private static void ShowStringBuilder() + { + // + // 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; + // + } + + private static void ShowEscapes() + { + // + // 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); + // + } + + private static void ShowEscEscape() + { + // + // 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)esc[0]); // 27 + // + } + + private static void ShowVerbatim() + { + // + // 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); + // + } + + private static void ShowUtf8Literal() + { + // + // Suffix u8 produces a ReadOnlySpan of the string's UTF-8 bytes. + // Common in HTTP, network protocols, and other byte-oriented APIs. + ReadOnlySpan verb = "POST"u8; + ReadOnlySpan body = """{"status":"ok"}"""u8; + + Console.WriteLine(verb.Length); // 4 + Console.WriteLine(body.Length); // 15 + // + } + + private static void ShowIndexing() + { + // + // 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(); + // + } + + private static void ShowEqualityIntro() + { + // + // 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 + // + } +} diff --git a/docs/csharp/fundamentals/strings/snippets/strings-overview/strings-overview.csproj b/docs/csharp/fundamentals/strings/snippets/strings-overview/strings-overview.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/strings-overview/strings-overview.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 3ad4544c39147..7314face5f6c0 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -82,6 +82,14 @@ items: href: fundamentals/null-safety/nullable-value-types.md - name: Null operators href: fundamentals/null-safety/null-operators.md + - name: Strings + items: + - name: Overview + href: fundamentals/strings/index.md + - name: Raw string literals + href: fundamentals/strings/raw-string-literals.md + - name: nameof operator + href: fundamentals/strings/nameof.md - name: Object-oriented programming items: - name: Classes, structs, and records From dccd43f95130a661037554455749ba44c0d41d2f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 11 May 2026 11:38:32 -0400 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/csharp/fundamentals/strings/index.md | 4 ++-- docs/csharp/fundamentals/strings/nameof.md | 2 +- docs/csharp/fundamentals/strings/raw-string-literals.md | 2 +- docs/csharp/fundamentals/strings/snippets/nameof/Program.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/csharp/fundamentals/strings/index.md b/docs/csharp/fundamentals/strings/index.md index d7112e8e9ae95..8c0f859e7efb9 100644 --- a/docs/csharp/fundamentals/strings/index.md +++ b/docs/csharp/fundamentals/strings/index.md @@ -83,7 +83,7 @@ A `string` is a sequence of UTF-16 *code units*. The indexer returns one or by grapheme cluster using . Plain `char` iteration works for most Western text and ASCII-dominant content. +For text that might contain emoji or characters outside the Basic Multilingual Plane, iterate by *rune* using or by grapheme cluster using . Plain `char` iteration works for most Western text and ASCII-dominant content. ## String equality @@ -95,7 +95,7 @@ For comparisons that need to be locale- or case-aware, pass an explicit Date: Mon, 11 May 2026 16:50:51 -0400 Subject: [PATCH 3/5] First major content review --- docs/csharp/fundamentals/strings/index.md | 35 +++++++++---- docs/csharp/fundamentals/strings/nameof.md | 2 + .../strings/raw-string-literals.md | 13 +++-- .../snippets/strings-overview/Program.cs | 52 ++++++++++++++++++- 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/docs/csharp/fundamentals/strings/index.md b/docs/csharp/fundamentals/strings/index.md index 8c0f859e7efb9..2ba3e4b2bafe9 100644 --- a/docs/csharp/fundamentals/strings/index.md +++ b/docs/csharp/fundamentals/strings/index.md @@ -11,13 +11,13 @@ ai-usage: ai-assisted > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. > -> **Coming from Java or C++?** A C# `string` is an immutable reference type backed by . UTF-16 is the in-memory encoding, similar to Java's `String`. Unlike C/C++, strings aren't null-terminated and don't decay into pointers. +> **Coming from Java or C++?** A C# `string` is an immutable reference type backed by . UTF-16 is the in-memory encoding, similar to Java's `String`. Unlike C/C++, strings aren't null-terminated and don't decay into pointers. -A *string* is a sequence of characters. In C#, `string` is the language keyword for the type. Every string literal you write produces a `System.String` instance. +A *string* is a sequence of characters. In C#, `string` is the language keyword for the 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 IL. +The `string` keyword and the `String` type name refer to the same type. They compile to identical intermediate language (IL). :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="StringKeyword"::: @@ -25,13 +25,13 @@ Prefer the `string` keyword in your own code. It's consistent with the other bui ## Strings are immutable -Once a `string` is created, its characters never change. Methods such as `ToUpperInvariant`, `Replace`, `Substring`, and `Trim` return a *new* string. The original instance is unchanged. +Once a `string` is created, its characters never change. Methods such as `ToUpperInvariant`, `Replace`, `Substring`, and `Trim` return a *new* string that contains the modified value. The original instance is unchanged. :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Immutability"::: Immutability makes strings safe to share across methods and threads, and it's why the `string` type behaves like a value in everyday use even though it's a reference type. -When you build a string from many small pieces in a loop, each `+` or `Concat` call allocates a new instance. For that scenario, use , which appends in place and produces a single string at the end: +When you build a string from many small pieces in a loop, each `+` or `Concat` call allocates a new instance. When you build a string from several components, use , which appends in place and produces a single string at the end: :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="StringBuilder"::: @@ -39,7 +39,12 @@ For a small fixed number of pieces, plain interpolation or `string.Concat` is cl ## String literals -C# offers four literal forms. Each is suited to different content. +C# offers four literal forms. Each is suited to different content. As a quick guide: + +- Reach for **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. +- Prefer **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 above to get an **interpolated string** when you need to embed values. ### Regular literals and escape sequences @@ -53,6 +58,8 @@ Beginning in C# 13, `\e` represents the **ESC** control character (U+001B). It's :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="EscEscape"::: +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: @@ -61,13 +68,23 @@ A verbatim literal is prefixed with `@`. Backslashes are treated literally, whic 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. See [Raw string literals](raw-string-literals.md) for the full rules. +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: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Raw"::: + +Raw string literals all but eliminate escape sequences and accommodate any formatting and quoting you need. See [Raw string literals](raw-string-literals.md) for the full rules. ### Interpolated strings -A `$` prefix turns a literal into an *interpolated string*. Expressions in `{}` holes are evaluated and their results inserted: `$"hello, {name}"`. Interpolation is the recommended way to compose strings from values in everyday code, and it works with raw string literals too — `$"""..."""`. +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: + +:::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Interpolated"::: + +Interpolation is the recommended way to compose strings from values in everyday code. ## UTF-8 string literals @@ -79,7 +96,7 @@ The bytes are emitted at compile time, so there's no runtime encoding cost. ## Indexing and `char` -A `string` is a sequence of UTF-16 *code units*. The indexer returns one , which represents a single UTF-16 code unit, not necessarily a complete Unicode code point. `Length` returns the count of code units. +A `string` is a sequence of UTF-16 *code units*. The indexer returns one , which represents a single UTF-16 code unit, not necessarily a complete Unicode code point. `Length` returns the count of code units. :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Indexing"::: diff --git a/docs/csharp/fundamentals/strings/nameof.md b/docs/csharp/fundamentals/strings/nameof.md index fc76b926a8e6d..b51de9f040fb2 100644 --- a/docs/csharp/fundamentals/strings/nameof.md +++ b/docs/csharp/fundamentals/strings/nameof.md @@ -10,6 +10,8 @@ ai-usage: ai-assisted > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete operator reference, see [`nameof`](../../language-reference/operators/nameof.md) in the language reference. +> +> **Coming from another language?** Other languages have similar features — Java's reflective `Class.getSimpleName()`, JavaScript's `Function.name` and `Object.keys`, Python's `__name__` and `vars()`, and Swift's `#function`/`#keyPath`. Unlike most of those, C#'s `nameof` is a pure compile-time construct. It uses no reflection, allocates nothing at runtime, and produces a constant `string` that's baked into the assembly. The `nameof` operator returns the textual identifier of a symbol — a variable, parameter, type, member, or namespace — as a compile-time `string` constant. Anywhere you'd otherwise hardcode an identifier as a string, use `nameof`: the compiler verifies that the symbol exists, and rename refactorings update the result automatically. diff --git a/docs/csharp/fundamentals/strings/raw-string-literals.md b/docs/csharp/fundamentals/strings/raw-string-literals.md index 69cd75e94ec6f..b651d9acd0afc 100644 --- a/docs/csharp/fundamentals/strings/raw-string-literals.md +++ b/docs/csharp/fundamentals/strings/raw-string-literals.md @@ -10,9 +10,14 @@ ai-usage: ai-assisted > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete grammar, see the [language reference](../../language-reference/tokens/raw-string.md). +> +> **Coming from another language?** C# raw string literals fill the same role as Python's and Rust's `r"..."` strings, Java's text blocks (`"""..."""`), and the back-tick template strings in JavaScript, TypeScript, and Go. The C# syntax is closest to Java's text blocks, with extra rules for variable-length delimiters and interpolation. A *raw string literal* is delimited by three or more double quotes. Inside the delimiters, every character is taken literally — quotes and backslashes don't need escaping, and newlines are preserved as written. Use raw strings for any string that contains quotes, backslashes, or multiple lines: JSON, XML, SQL, regular expressions, file paths, and code samples. +> [!WARNING] +> A raw string literal makes SQL easier to read, but it doesn't make SQL safer. Never concatenate or interpolate user-supplied values into a SQL command — that opens your application to SQL injection. Use parameterized commands instead: with , or the higher-level helpers in [Entity Framework Core](/ef/core/querying/raw-sql) and [Dapper](https://github.com/DapperLib/Dapper). The same caution applies to other injection-prone formats such as shell commands, LDAP filters, and HTML. + ## A literal that contains quotes and backslashes A regular literal needs escapes for `"` and `\`. A verbatim literal still needs `""` to embed a quote. A raw literal needs neither: @@ -23,19 +28,19 @@ Each form produces the same string, but the raw version reads exactly like the J ## Single-line raw strings -The opening and closing delimiters are each at least three double quotes. The content sits between them on the same line. Quotes and backslashes inside the content are literal: +The opening and closing delimiters are each at least three double quotes, and the closing delimiter must use the same number of quotes as the opening delimiter. The content sits between them on the same line. Quotes and backslashes inside the content are literal: :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="SingleLine"::: -A single-line raw string can't be empty between its delimiters, and it can't begin or end with a quote unless the delimiter has more quotes than the content. +A single-line raw string can't be empty between its delimiters. It can end with a double quote, but it can't start with one — the compiler treats a leading double quote as an additional opening delimiter character. If your content must start with a quote, use a multiline raw string literal instead, which places the content on its own line where a leading quote is unambiguous. ## Multiline raw strings -For multiline content, the opening `"""` ends the line and the closing `"""` starts its own. Everything between the two delimiters is the value of the string, exactly as written: +For multiline content, the opening delimiter ends the line and the closing delimiter starts its own. As with single-line raw strings, the delimiter is three or more double quotes, and the closing delimiter must use the same number of quotes as the opening one. Three quotes is the common case, but you can use four, five, or more when the content itself contains a run of `"""` (see [Content that contains `"""`](#content-that-contains-)). Everything between the two delimiters is the value of the string, exactly as written: :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="Multiline"::: -The newline immediately after the opening `"""` and the newline immediately before the closing `"""` are not part of the value. They're delimiter whitespace. +The newline immediately after the opening `"""` and the newline immediately before the closing `"""` are not part of the value. They're delimiter whitespace. Likewise, any whitespace to the left of the closing `"""` is stripped from every content line, so you can indent the literal to match its enclosing code block without that indentation appearing in the string. The next section covers this rule in detail. ## Indentation: the closing delimiter sets the margin diff --git a/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs b/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs index 548bcf9b18cb4..08da52a851f92 100644 --- a/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs +++ b/docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs @@ -12,6 +12,8 @@ public static void Main() ShowEscapes(); ShowEscEscape(); ShowVerbatim(); + ShowRaw(); + ShowInterpolated(); ShowUtf8Literal(); ShowIndexing(); ShowEqualityIntro(); @@ -80,7 +82,7 @@ private static void ShowEscEscape() string esc = "\e[31mError\e[0m: file missing"; Console.WriteLine(esc); // ESC[31mError ESC[0m: file missing - Console.WriteLine((int)esc[0]); // 27 + Console.WriteLine((int)'\e'); // 27 // } @@ -97,6 +99,54 @@ private static void ShowVerbatim() // } + private static void ShowRaw() + { + // + // 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); + // + } + + private static void ShowInterpolated() + { + // + // 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); + // + } + private static void ShowUtf8Literal() { // From 97932518e349f68a9568902d3708a50b28fbc8ff Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 May 2026 16:22:44 -0400 Subject: [PATCH 4/5] Final proofread. Proofread and add definitions for terms. --- docs/csharp/fundamentals/strings/index.md | 14 ++++++------ docs/csharp/fundamentals/strings/nameof.md | 12 +++++----- .../strings/raw-string-literals.md | 22 +++++++++---------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/csharp/fundamentals/strings/index.md b/docs/csharp/fundamentals/strings/index.md index 2ba3e4b2bafe9..ae03d74259e52 100644 --- a/docs/csharp/fundamentals/strings/index.md +++ b/docs/csharp/fundamentals/strings/index.md @@ -25,13 +25,13 @@ Prefer the `string` keyword in your own code. It's consistent with the other bui ## Strings are immutable -Once a `string` is created, its characters never change. Methods such as `ToUpperInvariant`, `Replace`, `Substring`, and `Trim` return a *new* string that contains the modified value. The original instance is unchanged. +*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. :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Immutability"::: -Immutability makes strings safe to share across methods and threads, and it's why the `string` type behaves like a value in everyday use even though it's a reference type. +Because strings are immutable, you can safely share them across methods and threads. This immutability explains why the `string` type behaves like a value in everyday use even though it's a reference type. -When you build a string from many small pieces in a loop, each `+` or `Concat` call allocates a new instance. When you build a string from several components, use , which appends in place and produces a single string at the end: +When you build a string from many small pieces in a loop, each `+` or `Concat` call creates a new instance. When you build a string from several components, use , which appends in place and produces a single string at the end: :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="StringBuilder"::: @@ -39,12 +39,12 @@ For a small fixed number of pieces, plain interpolation or `string.Concat` is cl ## String literals -C# offers four literal forms. Each is suited to different content. As a quick guide: +C# offers four literal forms. Each form suits different content. As a quick guide: -- Reach for **regular literals** for short, simple text with at most a few escape sequences. +- 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. -- Prefer **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 above to get an **interpolated string** when you need to embed values. +- 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 above literals to get an **interpolated string** when you need to embed values. ### Regular literals and escape sequences diff --git a/docs/csharp/fundamentals/strings/nameof.md b/docs/csharp/fundamentals/strings/nameof.md index b51de9f040fb2..73a6698e0e883 100644 --- a/docs/csharp/fundamentals/strings/nameof.md +++ b/docs/csharp/fundamentals/strings/nameof.md @@ -11,9 +11,9 @@ ai-usage: ai-assisted > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete operator reference, see [`nameof`](../../language-reference/operators/nameof.md) in the language reference. > -> **Coming from another language?** Other languages have similar features — Java's reflective `Class.getSimpleName()`, JavaScript's `Function.name` and `Object.keys`, Python's `__name__` and `vars()`, and Swift's `#function`/`#keyPath`. Unlike most of those, C#'s `nameof` is a pure compile-time construct. It uses no reflection, allocates nothing at runtime, and produces a constant `string` that's baked into the assembly. +> **Coming from another language?** Other languages have similar features. Java's reflective `Class.getSimpleName()`, JavaScript's `Function.name` and `Object.keys`, Python's `__name__` and `vars()`, and Swift's `#function`/`#keyPath`. Unlike most of those, C#'s `nameof` is a pure compile-time construct. It uses no reflection, allocates nothing at runtime, and produces a constant `string` that's baked into the assembly. -The `nameof` operator returns the textual identifier of a symbol — a variable, parameter, type, member, or namespace — as a compile-time `string` constant. Anywhere you'd otherwise hardcode an identifier as a string, use `nameof`: the compiler verifies that the symbol exists, and rename refactorings update the result automatically. +The `nameof` operator returns the textual identifier of a symbol, such as a variable, parameter, type, member, or namespace, as a compile-time `string` constant. Anywhere you'd otherwise hardcode an identifier as a string, use `nameof`: the compiler verifies that the symbol exists, and rename refactorings update the result automatically. ## What `nameof` returns @@ -21,7 +21,7 @@ The `nameof` operator returns the textual identifier of a symbol — a variable, :::code language="csharp" source="snippets/nameof/Program.cs" ID="Basic"::: -`nameof(customer.Name)` returns `"Name"`, not `"customer.Name"`. For the fully qualified expression, only the last identifier is captured. +The operand can also be a *qualified expression*, one that uses the dot operator to navigate from a containing scope to a member, such as `customer.Name`, `System.Console`, or `List.Enumerator`. In that case, only the last identifier is captured: `nameof(customer.Name)` returns `"Name"`, not `"customer.Name"`. ## Argument validation @@ -63,7 +63,7 @@ This is useful in logging, diagnostic messages, and attribute arguments where th ## Qualified names -For a qualified expression — a type with a namespace, or a member with a receiver — `nameof` returns only the *last* identifier: +For any qualified expression, `nameof` returns only the *last* identifier: :::code language="csharp" source="snippets/nameof/Program.cs" ID="QualifiedName"::: @@ -71,13 +71,13 @@ If you need the fully qualified name, use > **Coming from another language?** C# raw string literals fill the same role as Python's and Rust's `r"..."` strings, Java's text blocks (`"""..."""`), and the back-tick template strings in JavaScript, TypeScript, and Go. The C# syntax is closest to Java's text blocks, with extra rules for variable-length delimiters and interpolation. -A *raw string literal* is delimited by three or more double quotes. Inside the delimiters, every character is taken literally — quotes and backslashes don't need escaping, and newlines are preserved as written. Use raw strings for any string that contains quotes, backslashes, or multiple lines: JSON, XML, SQL, regular expressions, file paths, and code samples. +A *raw string literal* is delimited by three or more double quotes. Inside the delimiters, every character is taken literally. Quotes and backslashes don't need escaping, and newlines are preserved as written. Use raw strings for any string that contains quotes, backslashes, or multiple lines: JSON, XML, SQL, regular expressions, file paths, and code samples. > [!WARNING] -> A raw string literal makes SQL easier to read, but it doesn't make SQL safer. Never concatenate or interpolate user-supplied values into a SQL command — that opens your application to SQL injection. Use parameterized commands instead: with , or the higher-level helpers in [Entity Framework Core](/ef/core/querying/raw-sql) and [Dapper](https://github.com/DapperLib/Dapper). The same caution applies to other injection-prone formats such as shell commands, LDAP filters, and HTML. +> A raw string literal makes SQL easier to read, but it doesn't make SQL safer. Never concatenate or interpolate user-supplied values into a SQL command. That practice opens your application to SQL injection. Use parameterized commands instead: with , or the higher-level helpers in [Entity Framework Core](/ef/core/querying/raw-sql) and [Dapper](https://github.com/DapperLib/Dapper). The same caution applies to other injection-prone formats such as shell commands, LDAP filters, and HTML. ## A literal that contains quotes and backslashes @@ -32,7 +32,7 @@ The opening and closing delimiters are each at least three double quotes, and th :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="SingleLine"::: -A single-line raw string can't be empty between its delimiters. It can end with a double quote, but it can't start with one — the compiler treats a leading double quote as an additional opening delimiter character. If your content must start with a quote, use a multiline raw string literal instead, which places the content on its own line where a leading quote is unambiguous. +A single-line raw string can't be empty between its delimiters. It can end with a double quote, but it can't start with one. The compiler treats a leading double quote as an additional opening delimiter character. If your content must start with a quote, use a multiline raw string literal instead, which places the content on its own line where a leading quote is unambiguous. ## Multiline raw strings @@ -40,11 +40,11 @@ For multiline content, the opening delimiter ends the line and the closing delim :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="Multiline"::: -The newline immediately after the opening `"""` and the newline immediately before the closing `"""` are not part of the value. They're delimiter whitespace. Likewise, any whitespace to the left of the closing `"""` is stripped from every content line, so you can indent the literal to match its enclosing code block without that indentation appearing in the string. The next section covers this rule in detail. +The newline immediately after the opening `"""` and the newline immediately before the closing `"""` aren't part of the value. They're delimiter whitespace. Likewise, the compiler strips any whitespace to the left of the closing `"""` from every content line, so you can indent the literal to match its enclosing code block without that indentation appearing in the string. The next section covers this rule in detail. ## Indentation: the closing delimiter sets the margin -The column of the closing `"""` defines a left margin. Whitespace up to that column is stripped from every content line. This rule lets you indent the literal to match the surrounding code without polluting the value: +The column of the closing `"""` defines a left margin. The compiler strips whitespace up to that column from every content line. This rule lets you indent the literal to match the surrounding code without polluting the value: :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="Indentation"::: @@ -52,7 +52,7 @@ If a content line has fewer leading whitespace characters than the closing delim ## Content that contains `"""` -When the content itself contains a run of three or more quotes, increase the delimiter count. Use four quotes — or more — at the opening and closing. The delimiter count just has to exceed the longest run of quotes anywhere in the content: +When the content itself contains a run of three or more quotes, increase the delimiter count. Use four or more quotes at the opening and closing. The delimiter count just has to exceed the longest run of quotes anywhere in the content: :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="MoreQuotes"::: @@ -60,23 +60,23 @@ You can scale this up to any number of quotes. Five-quote and six-quote delimite ## Raw interpolated strings -A `$` prefix on a raw string enables interpolation. Expressions in `{}` holes are evaluated and their results inserted into the value: +Add a ` prefix to a raw string to enable interpolation. The expressions in `{}` holes are evaluated, and their results are inserted into the value: :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="RawInterpolated"::: -If the content also needs literal `{` or `}` characters — common when generating JSON, CSS, or code — add more `$` signs. Each `$` raises the number of braces required to mark an interpolation hole. With `$$`, single `{` and `}` are literal and only `{{...}}` is treated as a hole: +If the content also needs literal `{` or `}` characters, which are common when generating JSON, CSS, or code, add more ` signs. Each ` raises the number of braces required to mark an interpolation hole. With `$`, single `{` and `}` are literal and only `{{...}}` is treated as a hole: :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="DoubleDollarInterpolation"::: -You can use any number of `$` signs to disambiguate brace runs. Triple-`$` literals are rare but available. +Use any number of ` signs to disambiguate brace runs. Triple-` literals are rare but available. ## When to choose which literal Use a **raw string literal** whenever the content contains quotes, backslashes, or multiple lines. The result is shorter to read, easier to paste in or out of, and free of escape-sequence bugs. -Use a **regular string literal** for short, single-line values without quotes or backslashes — names, messages, format placeholders. +Use a **regular string literal** for short, single-line values without quotes or backslashes such as names, messages, format placeholders. -Use a **verbatim string literal** (`@"..."`) only when working with existing code that uses them. For new code, raw strings cover every case verbatim strings cover, with cleaner syntax for embedded quotes. +Use a **verbatim string literal** (`@"..."`) only when working with existing code that uses them. For new code, raw strings cover every case that verbatim strings cover, with cleaner syntax for embedded quotes. ## See also From 5e5124a3ba343ed54a673153f1ec7a8d8f2aafdd Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 May 2026 16:31:44 -0400 Subject: [PATCH 5/5] Fix lint. --- docs/csharp/fundamentals/strings/raw-string-literals.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/csharp/fundamentals/strings/raw-string-literals.md b/docs/csharp/fundamentals/strings/raw-string-literals.md index 8a44301da8a69..6eed427d99515 100644 --- a/docs/csharp/fundamentals/strings/raw-string-literals.md +++ b/docs/csharp/fundamentals/strings/raw-string-literals.md @@ -68,7 +68,7 @@ If the content also needs literal `{` or `}` characters, which are common when g :::code language="csharp" source="snippets/raw-string-literals/Program.cs" ID="DoubleDollarInterpolation"::: -Use any number of ` signs to disambiguate brace runs. Triple-` literals are rare but available. +Use any number of `$` signs to disambiguate brace runs. Triple-`$` literals are rare but available. ## When to choose which literal