[Everyday C#] PR 10 — Strings: overview, raw strings, nameof#53676
[Everyday C#] PR 10 — Strings: overview, raw strings, nameof#53676BillWagner wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new Strings section to the C# Fundamentals docs, including three new articles and supporting snippet projects, and wires the new content into the main C# TOC.
Changes:
- Added a new Strings node to
docs/csharp/toc.ymlwith entries for Overview, Raw string literals, andnameof. - Added three new Fundamentals articles under
docs/csharp/fundamentals/strings/. - Added three new snippet projects (one per article) under
docs/csharp/fundamentals/strings/snippets/.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/csharp/toc.yml | Adds the new Strings section and links to the three new articles. |
| docs/csharp/fundamentals/strings/index.md | New strings overview article referencing the overview snippet project. |
| docs/csharp/fundamentals/strings/raw-string-literals.md | New raw string literals concept article referencing the raw-string-literals snippet project (one include currently points at the wrong source file). |
| docs/csharp/fundamentals/strings/nameof.md | New nameof concept article referencing the nameof snippet project. |
| docs/csharp/fundamentals/strings/snippets/strings-overview/strings-overview.csproj | New snippet project for the strings overview article (matches template settings). |
| docs/csharp/fundamentals/strings/snippets/strings-overview/Program.cs | Overview examples for string basics, immutability, literals, UTF-8 literals, indexing, and equality. |
| docs/csharp/fundamentals/strings/snippets/raw-string-literals/raw-string-literals.csproj | New snippet project for the raw string literals article (matches template settings). |
| docs/csharp/fundamentals/strings/snippets/raw-string-literals/Program.cs | Raw string literal examples: single-line, multiline, indentation, quote runs, and raw interpolation. |
| docs/csharp/fundamentals/strings/snippets/nameof/nameof.csproj | New snippet project for the nameof article (matches template settings). |
| docs/csharp/fundamentals/strings/snippets/nameof/Program.cs | nameof examples for basic usage, guard clauses, ThrowIfNull, INotifyPropertyChanged, attributes, unbound generics, and qualified names. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Proofread and add definitions for terms.
|
|
||
| :::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). |
There was a problem hiding this comment.
Is this your guidance or Copilots? VS always prompts me to use the keyword even for static methods.
|
|
||
| :::code language="csharp" source="snippets/strings-overview/Program.cs" ID="Immutability"::: | ||
|
|
||
| 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. |
There was a problem hiding this comment.
| 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. | |
| 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. |
|
|
||
| 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 creates a new instance. When you build a string from several components, use <xref:System.Text.StringBuilder>, which appends in place and produces a single string at the end: |
There was a problem hiding this comment.
| 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 <xref:System.Text.StringBuilder>, 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. A better choice is to use <xref:System.Text.StringBuilder>, 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. |
There was a problem hiding this comment.
| For a small fixed number of pieces, plain interpolation or `string.Concat` is clearer and just as fast. | |
| For a small, fixed number of pieces, plain interpolation or `string.Concat` is clearer and just as fast. |
| - 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 above literals to get an **interpolated string** when you need to embed values. |
There was a problem hiding this comment.
| - Add a ` prefix to any of the above literals to get an **interpolated string** when you need to embed values. | |
| - Add a `$` prefix to any of the aforementioned literals to get an **interpolated string** when you need to embed values. |
|
|
||
| ## Property change notifications | ||
|
|
||
| Types that implement <xref:System.ComponentModel.INotifyPropertyChanged> 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`: |
There was a problem hiding this comment.
| Types that implement <xref:System.ComponentModel.INotifyPropertyChanged> 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`: | |
| Types that implement <xref:System.ComponentModel.INotifyPropertyChanged> 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` instead: |
| - [Strings overview](index.md) | ||
| - [Raw string literals](raw-string-literals.md) | ||
| - [`nameof` operator (language reference)](../../language-reference/operators/nameof.md) | ||
| - <xref:System.ArgumentNullException.ThrowIfNull%2A> |
There was a problem hiding this comment.
| - <xref:System.ArgumentNullException.ThrowIfNull%2A> | |
| - <xref:System.ArgumentNullException.ThrowIfNull*> |
| 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 practice opens your application to SQL injection. Use parameterized commands instead: <xref:System.Data.Common.DbCommand.CreateParameter%2A?displayProperty=nameWithType> with <xref:System.Data.Common.DbParameterCollection.Add%2A?displayProperty=nameWithType>, 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. |
There was a problem hiding this comment.
| > 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: <xref:System.Data.Common.DbCommand.CreateParameter%2A?displayProperty=nameWithType> with <xref:System.Data.Common.DbParameterCollection.Add%2A?displayProperty=nameWithType>, 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: <xref:System.Data.Common.DbCommand.CreateParameter*?displayProperty=nameWithType> with <xref:System.Data.Common.DbParameterCollection.Add*?displayProperty=nameWithType>, 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. |
|
|
||
| ## Raw interpolated strings | ||
|
|
||
| Add a ` prefix to a raw string to enable interpolation. The expressions in `{}` holes are evaluated, and their results are inserted into the value: |
There was a problem hiding this comment.
| Add a ` prefix to a raw string to enable interpolation. The expressions in `{}` holes are evaluated, and their results are 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, 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: |
There was a problem hiding this comment.
| 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: | |
| 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: |
Fixes #53552
PR 10 of the EverydayCSharp Fundamentals restructuring. Creates a new
Stringssection with three articles plus snippet projects and a TOC entry. Purely additive — no moves, no redirects (PRs 11/12 handle the migrations and their redirects).Steps
docs/csharp/fundamentals/null-safety/is present (template source) anddocs/csharp/fundamentals/strings/is absent. No changes.docs/csharp/fundamentals/strings/snippets/{strings-overview,raw-string-literals,nameof}/— each with a.csprojmirroring the null-safety template (net10.0, NRT, implicit usings,Exe) and aProgram.cscontaining// <SnippetID>regions for every example. Verify each runs locally withdotnet run. Steps 2a/2b/2c are independent of each other.ms.topic: overview): what a string is, immutability, literals (regular/escape/\e/verbatim), brief UTF-8 (u8) section,char/indexing note, common-operations table (prose only — no links to future PR 11/12 articles), see-also. Strong recommendation: preferstringkeyword. Depends on step 2a.ms.topic: concept-article): why raw strings, single-line, multiline + indentation rule, more-than-three quotes, raw interpolated ($"""/$$"""), when to choose raw vs verbatim vs regular. Strong recommendation: prefer raw for any string with quotes/backslashes/multiline. Depends on step 2b.ms.topic: concept-article): what it does, argument validation (ThrowIfNullthennameofforArgumentException),INotifyPropertyChangedusage,nameofin attributes (extended scope, used without explanation), unbound generics (C# 14, mentioned), what it returns for qualified names, recommendation to prefernameofover identifier strings. Depends on step 2c.Stringsgroup with three entries immediately after the existingNull safetyblock (around line 85), beforeObject-oriented programming. Depends on steps 3–5.template-overview.md/template-concept.md); confirm the four-tier[!TIP]audience block is present and routes Java/C++ readers and beginners; confirmai-usage: ai-assisted; confirm no F1/helpviewer keys. Depends on steps 3–5.new, collection expressions, raw strings where natural). Confirm no consecutive snippets in any article. Grep for any links to the future-PR paths (fundamentals/strings/{interpolation,search,split,concatenate,modify,compare}.md,fundamentals/tutorials/string-interpolation.md) and remove if present. Depends on step 7.Relevant files
[!TIP]block,:::code … ID=…:::snippet inclusion pattern.concept-articlefrontmatter and structure..csprojtemplate (net10.0, NRT, implicit usings).Stringsinsertion (afterNull safety)..openpublishing.redirection.csharp.json— no edits in this PR.Verification
docs/csharp/fundamentals/strings/snippets/*/folder, rundotnet run. Capture each program's output in the PR description (per repo guideline: standalone harness for verification, snippet remains the minimal published subset).xrefresolution failures (especially<xref:System.String>,<xref:System.Text.StringBuilder>,<xref:System.ArgumentNullException.ThrowIfNull%2A>).StringsTOC entries; confirm articles render and:::code:::includes resolve.grepthe three new.mdfiles for any of the future-PR paths listed in step 8 to confirm none slipped in.ai-usage: ai-assistedappears in all three articles and no F1/helpviewer keywords are present.Decisions
Program.cs+ 3.csproj+toc.yml(within the ≤10-file PR rule).\eC# 13, unbound generics innameofC# 14). Don't call out C# 6/11 introductions in prose.stringkeyword overString— consistency with other built-in keywords; nousing Systemdependency.nameofover hardcoded identifier strings — compiler-verified, refactor-safe, zero runtime cost.Span<char>manipulation beyond a brief mention (deferred to Focus/Advanced per Decision 2).Program.csper article. For thenameofexamples that need attributes on members (e.g.,[NotNullIfNotNull(nameof(input))]), keep them inside a top-levelProgram.csby declaring helper methods/types after the top-level statements — still a single file-based program.Further considerations
u8) coverage in the overview. Project map says "discuss in HTTP context." Recommendation: one short subsection with a single example producing aReadOnlySpan<byte>for an HTTP body. Alternative: omit entirely and defer to a future Focus article.StringBuildermention. Recommendation: prose-only note in the immutability section, no dedicated subsection. The "when to useStringBuildervs interpolation vsConcat" decision lives in the futurecoding-style/design-alternatives.md(PR 29) andconcatenate.md(PR 12).nameofvs[CallerArgumentExpression]framing. Recommendation: showArgumentNullException.ThrowIfNull(which uses[CallerArgumentExpression]internally) as the modern default for null guards, then introducenameoffor the cases the helpers don't cover (ArgumentException,PropertyChanged, attribute arguments). If you'd rather keep the article focused tightly onnameofand skip theThrowIfNullframing, say the word.Internal previews
nameofoperator