-
Notifications
You must be signed in to change notification settings - Fork 6.1k
[Everyday C#] - PR 11. String interpolation search split #53991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
BillWagner
wants to merge
9
commits into
dotnet:main
Choose a base branch
from
BillWagner:string-interpolation-search-split
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
69d3fd7
Create new snippets
BillWagner 5e012d8
Build main articles
BillWagner ca1e7c6
Move advanced material to lang ref
BillWagner be7858b
Update TOCs
BillWagner ff5d88f
Redirects and removal
BillWagner 532ae8c
Potential fix for pull request finding
BillWagner 38ec805
Fix warnings.
BillWagner aa5f266
content review
BillWagner 04e2d88
Fix warnings.
BillWagner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| --- | ||
| title: "String interpolation in C#" | ||
| description: Learn how to build result strings in C# with string interpolation by embedding formatted expression results directly in a string literal. | ||
| ms.date: 05/21/2026 | ||
| ms.topic: concept-article | ||
| ai-usage: ai-assisted | ||
| --- | ||
|
|
||
| # String interpolation in C\# | ||
|
|
||
| > [!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 another language?** String interpolation in C# works much like template literals in JavaScript (`` `${x}` ``) or f-strings in Python (`f"{x}"`). The expression inside `{}` can be any valid C# expression, and you can add format and alignment specifiers without leaving the string. | ||
|
|
||
| *String interpolation* lets you embed expressions directly in a string literal by prefixing the literal with `$`: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="general"::: | ||
|
|
||
| Each `{ }` is an *interpolation expression*. C# evaluates the expression, converts the result to a string by calling its `ToString` method, and substitutes the text into the result. If the expression evaluates to `null`, C# substitutes an empty string. Interpolated strings are a more readable alternative to <xref:System.String.Format*?displayProperty=nameWithType> and support the full [composite formatting](../../../standard/base-types/composite-formatting.md) feature set. | ||
|
|
||
| For the language-reference treatment of the syntax and the underlying handler types, see [interpolated string token](../../language-reference/tokens/interpolated.md). For performance-focused topics such as `Span<char>` interpolation and custom interpolated string handlers, see [String operations](../../language-reference/builtin-types/string-operations.md). | ||
|
|
||
| ## Apply a format string | ||
|
|
||
| To control how an expression result is formatted, follow the expression with a colon and a [standard or custom format string](../../../standard/base-types/formatting-types.md): | ||
|
|
||
| ```csharp | ||
| {<expression>:<formatString>} | ||
| ``` | ||
|
|
||
| The following example formats a date and a numeric value: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="FormatString"::: | ||
|
|
||
| ## Set the field width and alignment | ||
|
|
||
| To produce aligned output, follow the expression with a comma and a minimum field width. Positive widths right-align the value, and negative widths left-align it: | ||
|
|
||
| ```csharp | ||
| {<expression>,<width>} | ||
| ``` | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="alignment"::: | ||
|
|
||
| When you need both alignment and a format string, put alignment first: | ||
|
|
||
| ```csharp | ||
| {<expression>,<width>:<formatString>} | ||
| ``` | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="AlignmentAndFormat"::: | ||
|
|
||
| If the formatted value is longer than the requested width, C# ignores the width and emits the full value. | ||
|
|
||
| ## Escape braces and use escape sequences | ||
|
|
||
| Interpolated strings support the same escape sequences as ordinary string literals. To include a literal `{` or `}` character in the result, double it (`{{` or `}}`). | ||
|
|
||
| For paths and other strings that contain backslashes, prefer an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) (`$"""..."""`) over the older verbatim form (`$@"..."`). Raw string literals don't process escape sequences, so backslashes appear as-is: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="escapes"::: | ||
|
|
||
| ## Use a conditional expression | ||
|
|
||
| The colon has special meaning inside an interpolation expression, so wrap a [conditional operator](../../language-reference/operators/conditional-operator.md) in parentheses: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="conditional"::: | ||
|
|
||
| ## Span an expression across multiple lines | ||
|
|
||
| For better readability, break long interpolation expressions across multiple lines. The following example uses an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) so the expression body can wrap freely: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="newlines"::: | ||
|
|
||
| > [!NOTE] | ||
| > Multi-line interpolation expressions are available since C# 11. | ||
|
|
||
| ## Build constant strings | ||
|
|
||
| When every interpolated expression is itself a constant `string`, the whole interpolated string is also a `const`. That makes it usable for attribute arguments, `switch` patterns, and other contexts that require compile-time constants: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="ConstantInterpolated"::: | ||
|
|
||
| ## Format with a specific culture | ||
|
|
||
| By default, an interpolated string formats values using <xref:System.Globalization.CultureInfo.CurrentCulture?displayProperty=nameWithType>, which affects date, number, and currency representations. For deterministic output, pass an explicit culture to <xref:System.String.Create(System.IFormatProvider,System.Runtime.CompilerServices.DefaultInterpolatedStringHandler%40)?displayProperty=nameWithType>: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="culture"::: | ||
|
|
||
| For invariant output (logs, file formats, machine-readable data), pass <xref:System.Globalization.CultureInfo.InvariantCulture?displayProperty=nameWithType>: | ||
|
|
||
| :::code language="csharp" source="snippets/interpolation/Program.cs" id="invariant"::: | ||
|
|
||
| ## See also | ||
|
|
||
| - [Interpolated string token](../../language-reference/tokens/interpolated.md) | ||
| - [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md) | ||
| - [Composite formatting](../../../standard/base-types/composite-formatting.md) | ||
| - [Formatting types in .NET](../../../standard/base-types/formatting-types.md) | ||
| - <xref:System.String.Format*?displayProperty=nameWithType> | ||
| - <xref:System.FormattableString?displayProperty=nameWithType> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| --- | ||
| title: "Search strings in C#" | ||
| description: Learn how to find text within strings in C# with the Contains, StartsWith, EndsWith, IndexOf, and LastIndexOf methods, and how to choose the right StringComparison. | ||
| ms.date: 05/21/2026 | ||
| ms.topic: concept-article | ||
| ai-usage: ai-assisted | ||
| --- | ||
|
|
||
| # Search strings in C\# | ||
|
|
||
| > [!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 another language?** C# `string` methods such as `Contains`, `StartsWith`, and `IndexOf` parallel methods in Java's `String` and JavaScript's `String.prototype`. The key difference is that some C# searches default to **ordinal, case-sensitive** comparison. Others default to the current culture's semantics. For user-facing searches, you might want to pass a <xref:System.StringComparison> value. | ||
|
|
||
| The <xref:System.String> class includes methods that answer two everyday questions: | ||
|
|
||
| - *Does this string contain that text?* — use <xref:System.String.Contains*>, <xref:System.String.StartsWith*>, or <xref:System.String.EndsWith*>. | ||
| - *Where does that text occur?* — use <xref:System.String.IndexOf*> or <xref:System.String.LastIndexOf*>. | ||
|
|
||
| For regular expressions, span-based search over `ReadOnlySpan<char>`, and performance considerations, see [String operations](../../language-reference/builtin-types/string-operations.md). For an in-depth treatment of culture-aware comparison, see [Best practices for comparing strings](../../../standard/base-types/best-practices-strings.md). | ||
|
|
||
| ## Check whether a string contains text | ||
|
|
||
| Use `Contains`, `StartsWith`, or `EndsWith` to test for the presence of a substring: | ||
|
|
||
| :::code language="csharp" source="snippets/search/Program.cs" id="contains"::: | ||
|
|
||
| These methods default to **case-sensitive, ordinal** comparison. To accept user input or to ignore case for display text, pass a <xref:System.StringComparison> value such as <xref:System.StringComparison.CurrentCultureIgnoreCase?displayProperty=nameWithType> or <xref:System.StringComparison.OrdinalIgnoreCase?displayProperty=nameWithType>. | ||
|
|
||
| When you search for a single character, use the `char` overload of `Contains`. It avoids allocating a one-character string and is more direct: | ||
|
|
||
| :::code language="csharp" source="snippets/search/Program.cs" id="ContainsChar"::: | ||
|
|
||
| ## Locate the position of text | ||
|
|
||
| `IndexOf` returns the zero-based index of the first occurrence of a substring (or character), and `LastIndexOf` returns the index of the last occurrence. Both return `-1` when the search text isn't present. Combine them to extract the text between two markers: | ||
|
|
||
| :::code language="csharp" source="snippets/search/Program.cs" id="IndexOf"::: | ||
|
|
||
| When you need every occurrence rather than the first or last, iterate by passing the previous result plus one as the `startIndex` argument, or switch to a regular expression. | ||
|
|
||
| ## Choose the right comparison | ||
|
|
||
| Most search overloads accept an optional <xref:System.StringComparison> value. Pick it based on the kind of data you're searching: | ||
|
|
||
| - If you're searching identifiers, file paths, protocol tokens, or anything else machine-defined, use <xref:System.StringComparison.Ordinal>. | ||
| - If you're searching the same kind of machine-defined data but want case insensitivity, use <xref:System.StringComparison.OrdinalIgnoreCase>. | ||
| - If you're searching user-visible text where the current locale's rules should apply, use <xref:System.StringComparison.CurrentCulture>. | ||
| - If you're searching that same user-visible text and want to ignore case, use <xref:System.StringComparison.CurrentCultureIgnoreCase>. | ||
| - If you're searching persisted data that must compare the same on every machine and culture, use <xref:System.StringComparison.InvariantCulture> (rarely needed). | ||
|
|
||
| Ordinal comparison is the fastest option and the right default for anything that isn't natural-language text. Culture-aware comparison is significantly slower and can produce surprising results. For example, in some cultures the lowercase `i` doesn't match an uppercase `I`.Reserve it for searches that users perform against prose. | ||
|
|
||
| ## See also | ||
|
|
||
| - [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md) | ||
| - [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md) | ||
| - [Comparing strings](../../../standard/base-types/comparing.md) | ||
| - <xref:System.String?displayProperty=nameWithType> | ||
| - <xref:System.StringComparison?displayProperty=nameWithType> | ||
182 changes: 182 additions & 0 deletions
182
docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| using System.Globalization; | ||
|
|
||
| namespace Interpolation; | ||
|
|
||
| public static class Program | ||
| { | ||
| public static void Main() | ||
| { | ||
| General(); | ||
| Console.WriteLine(); | ||
| FormatString(); | ||
| Console.WriteLine(); | ||
| Alignment(); | ||
| Console.WriteLine(); | ||
| AlignmentAndFormat(); | ||
| Console.WriteLine(); | ||
| Escapes(); | ||
| Console.WriteLine(); | ||
| Conditional(); | ||
| Console.WriteLine(); | ||
| Newlines(); | ||
| Console.WriteLine(); | ||
| ConstantInterpolated(); | ||
| Console.WriteLine(); | ||
| Culture(); | ||
| Console.WriteLine(); | ||
| Invariant(); | ||
| } | ||
|
|
||
| private static void General() | ||
| { | ||
| // <general> | ||
| double a = 3; | ||
| double b = 4; | ||
| Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5 * a * b}"); | ||
| Console.WriteLine($"Length of the hypotenuse of the right triangle with legs of {a} and {b} is {CalculateHypotenuse(a, b)}"); | ||
| double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2); | ||
| // => Area of the right triangle with legs of 3 and 4 is 6 | ||
| // => Length of the hypotenuse of the right triangle with legs of 3 and 4 is 5 | ||
| // </general> | ||
| } | ||
|
|
||
| private static void FormatString() | ||
| { | ||
| // <FormatString> | ||
| var date = new DateTime(1731, 11, 25); | ||
| Console.WriteLine($"On {date:dddd, MMMM dd, yyyy} L. Euler introduced the letter e to denote {Math.E:F5}."); | ||
| // => On Sunday, November 25, 1731 L. Euler introduced the letter e to denote 2.71828. | ||
| // </FormatString> | ||
| } | ||
|
|
||
| private static void Alignment() | ||
| { | ||
| // <alignment> | ||
| var titles = new Dictionary<string, string>() | ||
| { | ||
| ["Doyle, Arthur Conan"] = "Hound of the Baskervilles, The", | ||
| ["London, Jack"] = "Call of the Wild, The", | ||
| ["Shakespeare, William"] = "Tempest, The" | ||
| }; | ||
|
|
||
| Console.WriteLine("Author and Title List"); | ||
| Console.WriteLine(); | ||
| Console.WriteLine($"|{"Author",-25}|{"Title",30}|"); | ||
| foreach (var title in titles) | ||
| { | ||
| Console.WriteLine($"|{title.Key,-25}|{title.Value,30}|"); | ||
| } | ||
| // => Author and Title List | ||
| // => | ||
| // => |Author | Title| | ||
| // => |Doyle, Arthur Conan |Hound of the Baskervilles, The| | ||
| // => |London, Jack | Call of the Wild, The| | ||
| // => |Shakespeare, William | Tempest, The| | ||
| // </alignment> | ||
| } | ||
|
|
||
| private static void AlignmentAndFormat() | ||
| { | ||
| // <AlignmentAndFormat> | ||
| const int NameAlignment = -9; | ||
| const int ValueAlignment = 7; | ||
| double a = 3; | ||
| double b = 4; | ||
| Console.WriteLine($"Three classical Pythagorean means of {a} and {b}:"); | ||
| Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|"); | ||
| Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|"); | ||
| Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|"); | ||
| // => Three classical Pythagorean means of 3 and 4: | ||
| // => |Arithmetic| 3.500| | ||
| // => |Geometric| 3.464| | ||
| // => |Harmonic | 3.429| | ||
| // </AlignmentAndFormat> | ||
| } | ||
|
|
||
| private static void Escapes() | ||
| { | ||
| // <escapes> | ||
| int[] xs = [1, 2, 7, 9]; | ||
| int[] ys = [7, 9, 12]; | ||
| Console.WriteLine($"Find the intersection of the {{{string.Join(", ", xs)}}} and {{{string.Join(", ", ys)}}} sets."); | ||
| // => Find the intersection of the {1, 2, 7, 9} and {7, 9, 12} sets. | ||
|
|
||
| var userName = "Jane"; | ||
| var stringWithEscapes = $"C:\\Users\\{userName}\\Documents"; | ||
| var rawInterpolated = $"""C:\Users\{userName}\Documents"""; | ||
| Console.WriteLine(stringWithEscapes); | ||
| Console.WriteLine(rawInterpolated); | ||
| // => C:\Users\Jane\Documents | ||
| // => C:\Users\Jane\Documents | ||
| // </escapes> | ||
| } | ||
|
|
||
| private static void Conditional() | ||
| { | ||
| // <conditional> | ||
| var rand = new Random(42); | ||
| for (int i = 0; i < 3; i++) | ||
| { | ||
| Console.WriteLine($"Coin flip: {(rand.NextDouble() < 0.5 ? "heads" : "tails")}"); | ||
| } | ||
| // </conditional> | ||
| } | ||
|
|
||
| private static void Newlines() | ||
| { | ||
| // <newlines> | ||
| int[] numbers = [3, 1, 4, 1, 5, 9, 2, 6]; | ||
| Console.WriteLine($""" | ||
| Total: { | ||
| numbers.Sum() | ||
| }, average: {numbers.Average():F2}. | ||
| """); | ||
|
BillWagner marked this conversation as resolved.
|
||
| // => Total: 31, average: 3.88. | ||
| // </newlines> | ||
| } | ||
|
|
||
| private static void ConstantInterpolated() | ||
| { | ||
| // <ConstantInterpolated> | ||
| const string Audience = "world"; | ||
| const string Greeting = $"Hello, {Audience}!"; | ||
| Console.WriteLine(Greeting); | ||
| // => Hello, world! | ||
| // </ConstantInterpolated> | ||
| } | ||
|
|
||
| private static void Culture() | ||
| { | ||
| // <culture> | ||
| CultureInfo[] cultures = | ||
| [ | ||
| CultureInfo.GetCultureInfo("en-US"), | ||
| CultureInfo.GetCultureInfo("en-GB"), | ||
| CultureInfo.GetCultureInfo("nl-NL"), | ||
| CultureInfo.InvariantCulture | ||
| ]; | ||
| var date = new DateTime(2026, 5, 21, 12, 35, 31); | ||
| var number = 31_415_926.536; | ||
| foreach (var culture in cultures) | ||
| { | ||
| var cultureSpecificMessage = string.Create(culture, $"{date,23}{number,20:N3}"); | ||
| Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}"); | ||
| } | ||
| // Output is similar to: | ||
| // => en-US 5/21/2026 12:35:31 PM 31,415,926.536 | ||
| // => en-GB 21/05/2026 12:35:31 31,415,926.536 | ||
| // => nl-NL 21-05-2026 12:35:31 31.415.926,536 | ||
| // => 05/21/2026 12:35:31 31,415,926.536 | ||
| // </culture> | ||
| } | ||
|
|
||
| private static void Invariant() | ||
| { | ||
| // <invariant> | ||
| var timestamp = new DateTime(2026, 5, 21, 15, 46, 24); | ||
| string message = string.Create(CultureInfo.InvariantCulture, $"Date and time in invariant culture: {timestamp}"); | ||
| Console.WriteLine(message); | ||
| // => Date and time in invariant culture: 05/21/2026 15:46:24 | ||
| // </invariant> | ||
| } | ||
| } | ||
8 changes: 8 additions & 0 deletions
8
docs/csharp/fundamentals/strings/snippets/interpolation/interpolation.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
| </Project> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.