From 71bcb1e3b172573251bd37c6427c4f08ef68f441 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 09:50:29 -0400 Subject: [PATCH 01/10] Create new snippets Build the projects for this PR. --- .../strings/snippets/interpolation/Program.cs | 182 +++++++++++++++++ .../interpolation/interpolation.csproj | 8 + .../strings/snippets/search/Program.cs | 65 ++++++ .../strings/snippets/search/search.csproj | 8 + .../strings/snippets/split/Program.cs | 191 ++++++++++++++++++ .../strings/snippets/split/split.csproj | 8 + .../snippets/string-operations/Program.cs | 90 +++++++++ .../string-operations.csproj | 8 + 8 files changed, 560 insertions(+) create mode 100644 docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs create mode 100644 docs/csharp/fundamentals/strings/snippets/interpolation/interpolation.csproj create mode 100644 docs/csharp/fundamentals/strings/snippets/search/Program.cs create mode 100644 docs/csharp/fundamentals/strings/snippets/search/search.csproj create mode 100644 docs/csharp/fundamentals/strings/snippets/split/Program.cs create mode 100644 docs/csharp/fundamentals/strings/snippets/split/split.csproj create mode 100644 docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs create mode 100644 docs/csharp/language-reference/builtin-types/snippets/string-operations/string-operations.csproj diff --git a/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs b/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs new file mode 100644 index 0000000000000..2f18baf2c3623 --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs @@ -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() + { + // + 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 + // + } + + private static void 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. + // + } + + private static void Alignment() + { + // + var titles = new Dictionary() + { + ["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| + // + } + + private static void 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| + // + } + + private static void 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 + // + } + + private static void Conditional() + { + // + var rand = new Random(42); + for (int i = 0; i < 3; i++) + { + Console.WriteLine($"Coin flip: {(rand.NextDouble() < 0.5 ? "heads" : "tails")}"); + } + // + } + + private static void Newlines() + { + // + int[] numbers = [3, 1, 4, 1, 5, 9, 2, 6]; + Console.WriteLine($""" + Total: { + numbers.Sum() + }, average: {numbers.Average():F2}. + """); + // => Total: 31, average: 3.88. + // + } + + private static void ConstantInterpolated() + { + // + const string Audience = "world"; + const string Greeting = $"Hello, {Audience}!"; + Console.WriteLine(Greeting); + // => Hello, world! + // + } + + private static void 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 + // + } + + private static void 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 + // + } +} diff --git a/docs/csharp/fundamentals/strings/snippets/interpolation/interpolation.csproj b/docs/csharp/fundamentals/strings/snippets/interpolation/interpolation.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/interpolation/interpolation.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/fundamentals/strings/snippets/search/Program.cs b/docs/csharp/fundamentals/strings/snippets/search/Program.cs new file mode 100644 index 0000000000000..b359230ccf359 --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/search/Program.cs @@ -0,0 +1,65 @@ +namespace SearchStrings; + +public static class Program +{ + public static void Main() + { + Contains(); + Console.WriteLine(); + ContainsChar(); + Console.WriteLine(); + IndexOfExample(); + } + + private static void Contains() + { + // + string factMessage = "Extension methods have all the capabilities of regular static methods."; + + // Write the string and include the quotation marks. + Console.WriteLine($"\"{factMessage}\""); + + // Default comparisons are case sensitive. + bool containsSearchResult = factMessage.Contains("extension"); + Console.WriteLine($"""Contains "extension"? {containsSearchResult}"""); + + // For user-facing searches, pass a StringComparison value to control case and culture. + bool ignoreCaseSearchResult = factMessage.StartsWith("extension", StringComparison.CurrentCultureIgnoreCase); + Console.WriteLine($"""Starts with "extension"? {ignoreCaseSearchResult} (ignoring case)"""); + + bool endsWithSearchResult = factMessage.EndsWith(".", StringComparison.Ordinal); + Console.WriteLine($"Ends with '.'? {endsWithSearchResult}"); + // => "Extension methods have all the capabilities of regular static methods." + // => Contains "extension"? False + // => Starts with "extension"? True (ignoring case) + // => Ends with '.'? True + // + } + + private static void ContainsChar() + { + // + string path = "/usr/local/bin"; + bool hasSlash = path.Contains('/'); + Console.WriteLine($"Path contains '/': {hasSlash}"); + // => Path contains '/': True + // + } + + private static void IndexOfExample() + { + // + string factMessage = "Extension methods have all the capabilities of regular static methods."; + + Console.WriteLine($"\"{factMessage}\""); + + // Extract the text between the first and last occurrence of "methods". + int first = factMessage.IndexOf("methods") + "methods".Length; + int last = factMessage.LastIndexOf("methods"); + string between = factMessage.Substring(first, last - first); + Console.WriteLine($"""Substring between "methods" and "methods": '{between}'"""); + // => "Extension methods have all the capabilities of regular static methods." + // => Substring between "methods" and "methods": ' have all the capabilities of regular static ' + // + } +} diff --git a/docs/csharp/fundamentals/strings/snippets/search/search.csproj b/docs/csharp/fundamentals/strings/snippets/search/search.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/search/search.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/fundamentals/strings/snippets/split/Program.cs b/docs/csharp/fundamentals/strings/snippets/split/Program.cs new file mode 100644 index 0000000000000..fb825d5673b0b --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/split/Program.cs @@ -0,0 +1,191 @@ +namespace SplitStrings; + +public static class Program +{ + public static void Main() + { + SplitWords(); + Console.WriteLine(); + IndexWords(); + Console.WriteLine(); + RepeatedSeparators(); + Console.WriteLine(); + MultiChar(); + Console.WriteLine(); + MultiCharGaps(); + Console.WriteLine(); + StringSeparators(); + Console.WriteLine(); + LimitCount(); + Console.WriteLine(); + TrimEntries(); + } + + private static void SplitWords() + { + // + string phrase = "The quick brown fox jumps over the lazy dog."; + string[] words = phrase.Split(' '); + + foreach (var word in words) + { + Console.WriteLine($"<{word}>"); + } + // => + // => + // => + // => + // => + // => + // => + // => + // => + // + } + + private static void IndexWords() + { + // + string phrase = "The quick brown fox jumps over the lazy dog."; + string[] words = phrase.Split(' '); + + for (int i = 0; i < words.Length; i++) + { + Console.WriteLine($"Index {i}: <{words[i]}>"); + } + // => Index 0: + // => Index 1: + // => Index 2: + // => ... + // + } + + private static void RepeatedSeparators() + { + // + string phrase = "The quick brown fox jumps over the lazy dog."; + string[] words = phrase.Split(' '); + + foreach (var word in words) + { + Console.WriteLine($"<{word}>"); + } + // The runs of spaces produce empty entries: + // => + // => + // => + // => <> + // => <> + // => <> + // => + // => ... + // + } + + private static void MultiChar() + { + // + char[] delimiters = [' ', ',', '.', ':', '\t']; + + string text = "one\ttwo three:four,five six seven"; + Console.WriteLine($"Original text: '{text}'"); + + string[] words = text.Split(delimiters); + Console.WriteLine($"{words.Length} words in text:"); + + foreach (var word in words) + { + Console.WriteLine($"<{word}>"); + } + // => 7 words in text: + // => + // => + // => + // => + // => + // => + // => + // + } + + private static void MultiCharGaps() + { + // + char[] delimiters = [' ', ',', '.', ':', '\t']; + + string text = "one\ttwo :,five six seven"; + Console.WriteLine($"Original text: '{text}'"); + + string[] words = text.Split(delimiters); + Console.WriteLine($"{words.Length} words in text:"); + + foreach (var word in words) + { + Console.WriteLine($"<{word}>"); + } + // Adjacent delimiters produce empty entries — use StringSplitOptions.RemoveEmptyEntries to drop them. + // + } + + private static void StringSeparators() + { + // + string[] separators = ["<<", "..."]; + + string text = "one< 3 substrings in text: + // => one + // => two + // => three + } + + private static void LimitCount() + { + // + string phrase = "The quick brown fox jumps over the lazy dog."; + string[] words = phrase.Split(' ', 4, StringSplitOptions.None); + + foreach (var word in words) + { + Console.WriteLine($"<{word}>"); + } + // => + // => + // => + // => + // + } + + private static void TrimEntries() + { + // + string numerals = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"; + string[] trimmed = numerals.Split(',', StringSplitOptions.TrimEntries); + + Console.WriteLine("Trimmed entries:"); + foreach (var word in trimmed) + { + Console.WriteLine($"<{word}>"); + } + + string[] untrimmed = numerals.Split(',', StringSplitOptions.None); + Console.WriteLine("Untrimmed entries:"); + foreach (var word in untrimmed) + { + Console.WriteLine($"<{word}>"); + } + // => Trimmed entries: <1> <2> ... <10> + // => Untrimmed entries: <1> < 2> ... < 10> + // + } +} diff --git a/docs/csharp/fundamentals/strings/snippets/split/split.csproj b/docs/csharp/fundamentals/strings/snippets/split/split.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/split/split.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs b/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs new file mode 100644 index 0000000000000..80c99e773e04f --- /dev/null +++ b/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs @@ -0,0 +1,90 @@ +using System.Text.RegularExpressions; + +namespace StringOperations; + +public static class Program +{ + public static void Main() + { + RegexPattern(); + Console.WriteLine(); + RegexValidate(); + Console.WriteLine(); + SpanSearch(); + } + + private static void RegexPattern() + { + // + string[] sentences = + [ + "Put the water over there.", + "They're quite thirsty.", + "Their water bottles broke." + ]; + + string pattern = @"the(ir)?\s"; + + foreach (string s in sentences) + { + Console.Write($"{s,28}"); + + if (Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase)) + { + Console.WriteLine($" (match for '{pattern}' found)"); + } + else + { + Console.WriteLine(); + } + } + // + } + + private static void RegexValidate() + { + // + string[] numbers = + [ + "123-555-0190", + "444-234-22450", + "690-555-0178", + "146-893-232", + "146-555-0122", + "4007-555-0111", + "407-555-0111", + "407-2-5555", + "407-555-8974", + "407-2ab-5555", + "690-555-8148", + "146-893-232-" + ]; + + string pattern = """^\d{3}-\d{3}-\d{4}$"""; + + foreach (string s in numbers) + { + Console.Write($"{s,14}"); + Console.WriteLine(Regex.IsMatch(s, pattern) ? " - valid" : " - invalid"); + } + // + } + + private static void SpanSearch() + { + // + ReadOnlySpan input = "key1=alpha;key2=beta;key3=gamma".AsSpan(); + ReadOnlySpan needle = "key2=".AsSpan(); + + int start = input.IndexOf(needle); + if (start >= 0) + { + ReadOnlySpan rest = input[(start + needle.Length)..]; + int end = rest.IndexOf(';'); + ReadOnlySpan value = end >= 0 ? rest[..end] : rest; + Console.WriteLine($"key2 = {value}"); + } + // => key2 = beta + // + } +} diff --git a/docs/csharp/language-reference/builtin-types/snippets/string-operations/string-operations.csproj b/docs/csharp/language-reference/builtin-types/snippets/string-operations/string-operations.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/language-reference/builtin-types/snippets/string-operations/string-operations.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + From 412a56b39d98102d5cc34824d09c1fd1071bf253 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 09:59:27 -0400 Subject: [PATCH 02/10] Build main articles Build the main articles for this PR. --- .../fundamentals/strings/interpolation.md | 102 ++++++++++++++++++ docs/csharp/fundamentals/strings/search.md | 63 +++++++++++ docs/csharp/fundamentals/strings/split.md | 82 ++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 docs/csharp/fundamentals/strings/interpolation.md create mode 100644 docs/csharp/fundamentals/strings/search.md create mode 100644 docs/csharp/fundamentals/strings/split.md diff --git a/docs/csharp/fundamentals/strings/interpolation.md b/docs/csharp/fundamentals/strings/interpolation.md new file mode 100644 index 0000000000000..341cb2cba1acc --- /dev/null +++ b/docs/csharp/fundamentals/strings/interpolation.md @@ -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 expression results 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 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` 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 +{:} +``` + +The following example formats a date and a numeric value: + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="format-string"::: + +## 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; negative widths left-align it: + +```csharp +{,} +``` + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="alignment"::: + +When you need both alignment and a format string, put alignment first: + +```csharp +{,:} +``` + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="alignment-and-format"::: + +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 [ternary 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, you can break long interpolation expressions across multiple lines. The example below 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="constant-interpolated"::: + +## Format with a specific culture + +By default, an interpolated string formats values using , which affects date, number, and currency representations. For deterministic output, pass an explicit culture to : + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="culture"::: + +For invariant output (logs, file formats, machine-readable data), pass : + +:::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) +- +- diff --git a/docs/csharp/fundamentals/strings/search.md b/docs/csharp/fundamentals/strings/search.md new file mode 100644 index 0000000000000..ca12d30f363c6 --- /dev/null +++ b/docs/csharp/fundamentals/strings/search.md @@ -0,0 +1,63 @@ +--- +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 C# searches default to **ordinal, case-sensitive** comparison; for user-facing searches you might want to pass a value. + +The class includes methods that answer two everyday questions: + +- *Does this string contain that text?* — use , , or . +- *Where does that text occur?* — use or . + +For pattern matching (regular expressions), span-based search over `ReadOnlySpan`, 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 value such as or . + +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="contains-char"::: + +## 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="index-of"::: + +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 value. Pick it based on the kind of data you're searching: + +| Scenario | Recommended value | +|-------------------------------------------------------------------------|-----------------------------------------------------------------| +| Identifiers, file paths, protocol tokens, anything machine-defined | | +| Same as above, but you want case insensitivity | | +| User-visible text where the current locale's rules should apply | | +| Same as above, ignoring case | | +| Persisted data that must compare the same on every machine and culture | (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` — so reserve it for searches that real users perform against real 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) +- +- diff --git a/docs/csharp/fundamentals/strings/split.md b/docs/csharp/fundamentals/strings/split.md new file mode 100644 index 0000000000000..b88ef590dade2 --- /dev/null +++ b/docs/csharp/fundamentals/strings/split.md @@ -0,0 +1,82 @@ +--- +title: "Split strings into substrings in C#" +description: Learn how to split a C# string into substrings with String.Split, including how to use multiple separators, limit the substring count, and trim or remove entries. +ms.date: 05/21/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Split strings into substrings 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.Split` is C#'s counterpart to Java's `String.split` and JavaScript's `String.prototype.split`. Unlike those languages, C# returns an array (`string[]`), not a list, and the separator argument is a character or string — never a regular expression. For pattern-based splitting, see . + +The method breaks a string into an array of substrings using one or more separators. It's the simplest way to parse delimited text such as words, CSV-style values, or protocol tokens. + +The method has many overloads, but they cover four independent decisions: + +- **Separators**: one `char`, an array of `char`, one `string`, or an array of `string`. +- **Maximum result count**: cap the number of substrings returned. +- **Empty-entry handling**: keep empty substrings (the default) or drop them with . +- **Whitespace handling**: trim leading and trailing whitespace from each entry with . + +The rest of this article walks through the common combinations. + +## Split a string into words + +To split a phrase on whitespace, pass `' '` as the separator: + +:::code language="csharp" source="snippets/split/Program.cs" id="split-words"::: + +The returned array is zero-indexed, so you can iterate over it with `for` to recover the position of each word: + +:::code language="csharp" source="snippets/split/Program.cs" id="index-words"::: + +If the input contains runs of the separator character, `Split` produces empty entries — one for each "gap" between consecutive separators: + +:::code language="csharp" source="snippets/split/Program.cs" id="repeated-separators"::: + +Pass `StringSplitOptions.RemoveEmptyEntries` to drop those empty entries (shown later). + +## Split on multiple separator characters + +When more than one character can act as a separator, pass them as an array. The following example treats spaces, commas, periods, colons, and tabs all as word boundaries: + +:::code language="csharp" source="snippets/split/Program.cs" id="multi-char"::: + +Adjacent separators still produce empty entries: + +:::code language="csharp" source="snippets/split/Program.cs" id="multi-char-gaps"::: + +## Split on multi-character separators + +To split on whole-word or multi-character separators, pass an array of strings together with a value: + +:::code language="csharp" source="snippets/split/Program.cs" id="string-separators"::: + +## Limit how many substrings you get back + +Pass a `count` argument to cap the number of results. The final entry holds everything that's left, including any remaining separators: + +:::code language="csharp" source="snippets/split/Program.cs" id="limit-count"::: + +This pattern is handy for `key=value` pairs and other formats where only the first separator is meaningful. + +## Trim whitespace from each entry + +`StringSplitOptions.TrimEntries` strips leading and trailing whitespace from every returned substring. You can combine it with `RemoveEmptyEntries` for typical CSV-style cleanup: + +:::code language="csharp" source="snippets/split/Program.cs" id="trim-entries"::: + +## When to reach for a regular expression + +`Split` works well for fixed character or string delimiters. For pattern-based splitting — for example, "split on any run of whitespace" or "split on either `;` or `, `" — use . See [String operations](../../language-reference/builtin-types/string-operations.md) for an introduction to regular expressions on strings. + +## See also + +- +- +- [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md) +- [Extract elements from a string](../../standard/base-types/divide-up-strings.md) From bb7411006377b2c6b9266ed324f16960fb844061 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 10:07:57 -0400 Subject: [PATCH 03/10] Move advanced material to lang ref Move the advanced material into an appropriate language reference article. This information doesn't fit the fundamentals goals, but is important for more advanced developers. --- .../builtin-types/string-operations.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/csharp/language-reference/builtin-types/string-operations.md diff --git a/docs/csharp/language-reference/builtin-types/string-operations.md b/docs/csharp/language-reference/builtin-types/string-operations.md new file mode 100644 index 0000000000000..4c44307f2a22f --- /dev/null +++ b/docs/csharp/language-reference/builtin-types/string-operations.md @@ -0,0 +1,70 @@ +--- +title: "String operations: pattern matching, performance, and span-based search" +description: "Learn how to apply regular-expression patterns to strings, search strings with ReadOnlySpan, and pick the right StringComparison for performance." +ms.date: 05/21/2026 +--- + +# String operations: pattern matching, performance, and span-based search + +This article covers string operations that go beyond the everyday `Contains`/`IndexOf`/`Split` methods covered in the Fundamentals [Search strings](../../fundamentals/strings/search.md) and [Split strings into substrings](../../fundamentals/strings/split.md) articles. It focuses on three areas: regular-expression pattern matching, allocation-free search over , and performance considerations for `string` comparison. + +## Find specific text using regular expressions + +The class searches strings for patterns rather than fixed substrings. The static method takes the input string, a pattern, and optional flags. + +The following example searches each sentence for the word *the* or *their*, case insensitive. The pattern `the(ir)?\s` matches `the` optionally followed by `ir`, then a whitespace character: + +| Pattern | Meaning | +|----------|----------------------------------| +| `the` | match the literal text `the` | +| `(ir)?` | match 0 or 1 occurrence of `ir` | +| `\s` | match a whitespace character | + +:::code language="csharp" source="snippets/string-operations/Program.cs" id="regex-pattern"::: + +## Validate strings against a pattern + +To check whether an entire input matches a shape, anchor the pattern with `^` and `$`. The following example validates that each string is a US-style telephone number — three digits, three digits, four digits, separated by dashes: + +| Pattern | Meaning | +|---------|----------------------------------------| +| `^` | match the beginning of the string | +| `\d{3}` | match exactly three digit characters | +| `-` | match a literal `-` character | +| `\d{4}` | match exactly four digit characters | +| `$` | match the end of the string | + +:::code language="csharp" source="snippets/string-operations/Program.cs" id="regex-validate"::: + +For the full pattern syntax, see [Regular expression language — quick reference](../../../standard/base-types/regular-expression-language-quick-reference.md). + +## Choose between `string` methods and regular expressions + +`string` methods and `Regex` solve overlapping problems. Prefer `string` methods when the text you're searching for is a literal value, a known prefix or suffix, or a fixed delimiter — they're simpler to read and faster, because they don't pay the cost of compiling and executing a pattern. Reach for `Regex` when the search target is a *shape* — alternations, optional groups, repeated character classes, or anchored validation. As a rule of thumb, if you can write the search as one or two `string.Contains` / `StartsWith` / `IndexOf` calls, do so. + +## Search using `ReadOnlySpan` + +When you parse large inputs or run a search on a hot path, the per-call allocations of `string.Substring` and `string.Split` can dominate. `ReadOnlySpan` gives you a view over an existing string (or array, or stack buffer) without copying, and provides span-based equivalents of the common `string` methods, including : + +:::code language="csharp" source="snippets/string-operations/Program.cs" id="span-search"::: + +Span-based search avoids allocations because the slices (`input[start..]`, `rest[..end]`) are simply windows over the original characters. The same approach scales to parsing key-value lists, headers, and other delimited text without ever calling `Substring`. + +## Performance considerations for `StringComparison` + +Most `string` instance methods have overloads that accept a value. The default for methods such as and is **ordinal**, but the default for is **current culture**. The difference matters in two ways: + +- **Speed.** Ordinal comparison is a byte-for-byte test that runs in tight, vectorized loops. Culture-aware comparison consults a sort table, walks combining characters, and applies locale-specific rules — it can be an order of magnitude slower for the same input. +- **Correctness.** Culture-aware comparison can fold characters that you don't expect (Turkish `i`/`I`, German `ß` to `ss`, ligatures), which is the right behavior for sorting names a user sees but the wrong behavior for parsing identifiers, paths, or protocol tokens. + +For machine-defined text — file names, URLs, HTTP headers, identifiers, configuration keys — pass or explicitly. Reserve culture-aware values for natural-language text shown to users. For comprehensive guidance, see [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md). + +## See also + +- [Regular expression language — quick reference](../../../standard/base-types/regular-expression-language-quick-reference.md) +- [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md) +- [Search strings](../../fundamentals/strings/search.md) +- [Split strings into substrings](../../fundamentals/strings/split.md) +- +- +- From e20a91d98dabbe183565a438be6090b4e9af39c3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 10:10:13 -0400 Subject: [PATCH 04/10] Update TOCs --- docs/csharp/language-reference/toc.yml | 3 +++ docs/csharp/toc.yml | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 1d6403142551f..baaca74199f03 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -65,6 +65,9 @@ items: - name: Nullable reference types href: ./builtin-types/nullable-reference-types.md displayName: "? token, ? symbol" + - name: String operations + displayName: regex, ReadOnlySpan, StringComparison + href: ./builtin-types/string-operations.md - name: Collections and arrays items: - name: Collections diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index f122ed37af35b..0d7bf838777ff 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -94,8 +94,14 @@ items: href: fundamentals/strings/index.md - name: Raw string literals href: fundamentals/strings/raw-string-literals.md + - name: String interpolation + href: fundamentals/strings/interpolation.md - name: nameof operator href: fundamentals/strings/nameof.md + - name: Search strings + href: fundamentals/strings/search.md + - name: Split strings into substrings + href: fundamentals/strings/split.md - name: Object-oriented programming items: - name: Classes, structs, and records @@ -341,12 +347,8 @@ items: - name: Article index displayName: how to's href: how-to/index.md - - name: Split strings into substrings - href: how-to/parse-strings-using-split.md - name: Concatenate strings href: how-to/concatenate-multiple-strings.md - - name: Search strings - href: how-to/search-strings.md - name: Modify string contents href: how-to/modify-string-contents.md - name: Compare strings From 7000467f718667447c16745b2265e925aca477a4 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 10:17:33 -0400 Subject: [PATCH 05/10] Redirects and removal Remove (now) obsolete articles. Add necessary redirects. --- .openpublishing.redirection.csharp.json | 8 + .../how-to/parse-strings-using-split.md | 103 ----------- docs/csharp/how-to/search-strings.md | 72 -------- .../strings/ParseStringsUsingSplit.cs | 165 ------------------ .../csharp/how-to/snippets/strings/Program.cs | 6 - .../how-to/snippets/strings/SearchStrings.cs | 117 ------------- 6 files changed, 8 insertions(+), 463 deletions(-) delete mode 100644 docs/csharp/how-to/parse-strings-using-split.md delete mode 100644 docs/csharp/how-to/search-strings.md delete mode 100644 docs/csharp/how-to/snippets/strings/ParseStringsUsingSplit.cs delete mode 100644 docs/csharp/how-to/snippets/strings/SearchStrings.cs diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index 7ea1775aa00bd..2cc0bfa600c15 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -663,10 +663,18 @@ "source_path_from_root": "/docs/csharp/getting-started/with-visual-studio.md", "redirect_url": "/dotnet/core/tutorials/with-visual-studio" }, + { + "source_path_from_root": "/docs/csharp/how-to/parse-strings-using-split.md", + "redirect_url": "/dotnet/csharp/fundamentals/strings/split" + }, { "source_path_from_root": "/docs/csharp/how-to/safely-cast-using-pattern-matching-is-and-as-operators.md", "redirect_url": "/dotnet/csharp/fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators" }, + { + "source_path_from_root": "/docs/csharp/how-to/search-strings.md", + "redirect_url": "/dotnet/csharp/fundamentals/strings/search" + }, { "source_path_from_root": "/docs/csharp/implicitly-typed-lambda-expressions.md", "redirect_url": "/dotnet/csharp/language-reference/operators/lambda-expressions" diff --git a/docs/csharp/how-to/parse-strings-using-split.md b/docs/csharp/how-to/parse-strings-using-split.md deleted file mode 100644 index 444c3161c1e38..0000000000000 --- a/docs/csharp/how-to/parse-strings-using-split.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: "Divide strings using String.Split" -description: The Split method returns an array of strings split from a set of delimiters. It's an easy way to extract substrings from a string. -ms.date: 12/05/2025 -helpviewer_keywords: - - "splitting strings [C#]" - - "Split method [C#]" - - "strings [C#], splitting" - - "parse strings" -ms.custom: copilot-scenario-highlight ---- -# How to separate strings using String.Split in C\# - -The method creates an array of substrings by splitting the input string based on one or more delimiters. This method is often the easiest way to separate a string on word boundaries. - -> [!TIP] -> You can use AI assistance to [split a string](#use-ai-to-split-a-string). - -## Split a string into words - -The following code splits a common phrase into an array of strings for each word. - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet1"::: - -Every instance of a separator character produces a value in the returned array. Since arrays in C# are zero-indexed, each string in the array is indexed from 0 to the value returned by the property minus 1: - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet1.5"::: - -The has many overloads. These overloads customize the behavior for splitting strings: - -- You can specify separators as `char` values or `string` values. -- You can specify one separator or multiple separators. If you specify multiple separators, they must all be the same type (either `char` or `string`). -- You can specify the maximum number of substrings to return. -- You can specify if repeated separator characters are ignored, or produce empty substrings in the return value. -- You can specify if leading and trailing whitespace is removed from the returned substrings. - -The remaining examples use different overloads to show each of these behaviors. - -For more information about indices, see the [Explore indexes and ranges](../tutorials/ranges-indexes.md) article. - -## Specify multiple separators - - can use multiple separator characters. The following example uses spaces, commas, periods, colons, and tabs as separating characters, which are passed to in an array. The loop at the bottom of the code displays each of the words in the returned array. - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet3"::: - -Consecutive instances of any separator produce the empty string in the output array: - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet4"::: - - can take an array of strings (character sequences that act as separators for parsing the target string, instead of single characters). - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet5"::: - -## Limit output size - -The following example shows how to limit the output to the first four substrings in the source string. - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet6"::: - -## Remove empty substrings - -Consecutive separator characters produce the empty string as a value in the returned array. You can see how an empty string is created in the following example, which uses the space character as a separator. - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet2"::: - -This behavior makes it easier for formats like comma-separated values (CSV) files representing tabular data. Consecutive commas represent a blank column. - -You can pass an optional parameter to exclude any empty strings in the returned array. For more complicated processing of the returned collection, you can use [LINQ](../linq/index.md) to manipulate the result sequence. - -## Trim whitespace - -The following example shows the effect of trimming entries: - -:::code language="csharp" source="./snippets/strings/ParseStringsUsingSplit.cs" id="Snippet7"::: - -The untrimmed entries have extra whitespace before the numerals. - -## Use AI to split a string - -You can use AI tools, such as GitHub Copilot, to generate code to split strings using `String.Split` in C#. You can customize the prompt to use strings and delimiters per your requirements. - -The following text shows an example prompt for Copilot Chat: - -```copilot-prompt -Generate C# code to parse and split CSV-style data from user input or external export that isn't well formed. -The input string may contain mixed delimiters (commas, semicolons), inconsistent spacing and empty entries. -Parse the string: accept all valid separators, remove any empty values and trim whitespace from all entries. -Return a clean list of values. -Show example output for the string: " apples, oranges ; bananas, , ;pears". -``` - -Review Copilot's suggestions before applying them. - -For more information, see [Copilot FAQs](https://aka.ms/copilot-general-use-faqs). - -## See also - -- [Extract elements from a string](../../standard/base-types/divide-up-strings.md) -- [Strings](../programming-guide/strings/index.md) -- [.NET regular expressions](../../standard/base-types/regular-expressions.md) -- [GitHub Copilot in Visual Studio](/visualstudio/ide/visual-studio-github-copilot-install-and-states) -- [GitHub Copilot in VS Code](https://code.visualstudio.com/docs/copilot/overview) diff --git a/docs/csharp/how-to/search-strings.md b/docs/csharp/how-to/search-strings.md deleted file mode 100644 index 53dab76b473ed..0000000000000 --- a/docs/csharp/how-to/search-strings.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: "How to search strings" -description: Learn about two strategies to search for text in strings in C#. String class methods search for specific text. Regular expressions search for patterns in text. -ms.date: 02/18/2025 -helpviewer_keywords: - - "searching strings [C#]" - - "strings [C#], searching with String methods" - - "strings [C#], searching with regular expressions" ---- - -# How to search strings - -You can use two main strategies to search for text in strings. Methods of the class search for specific text. Regular expressions search for patterns in text. - -The [string](../language-reference/builtin-types/reference-types.md#the-string-type) type, which is an alias for the class, provides many useful methods for searching the contents of a string. Among them are , , , , . The class provides a rich vocabulary to search for patterns in text. In this article, you learn these techniques and how to choose the best method for your needs. - -## Does a string contain text? - -The , , and methods search a string for specific text. The following example shows each of these methods and a variation that uses a case-insensitive search: - -:::code language="csharp" source="./snippets/strings/SearchStrings.cs" id="Snippet1"::: - -The preceding example demonstrates an important point for using these methods. Searches are **case-sensitive** by default. You use the enumeration value to specify a case-insensitive search. - -## Where does the sought text occur in a string? - -The and methods also search for text in strings. These methods return the location of the text being sought. If the text isn't found, they return `-1`. The following example shows a search for the first and last occurrence of the word "methods" and displays the text in between. - -:::code language="csharp" source="./snippets/strings/SearchStrings.cs" id="Snippet2"::: - -## Finding specific text using regular expressions - -The class can be used to search strings. These searches can range in complexity from simple to complicated text patterns. - -The following code example searches for the word "the" or "their" in a sentence, ignoring case. The static method performs the search. You give it the string to search and a search pattern. In this case, a third argument specifies case-insensitive search. For more information, see . - -The search pattern describes the text you search for. The following table describes each element of the search pattern. (The following table uses the single `\`, which must be escaped as `\\` in a C# string). - -| Pattern | Meaning | -|----------|----------------------------------| -| `the` | match the text "the" | -| `(eir)?` | match 0 or 1 occurrence of "eir" | -| `\s` | match a white-space character | - -:::code language="csharp" source="./snippets/strings/SearchStrings.cs" id="Snippet3"::: - -> [!TIP] -> The `string` methods are generally better choices when you're searching for an exact string. Regular expressions are better when you're searching for some pattern in a source string. - -## Does a string follow a pattern? - -The following code uses regular expressions to validate the format of each string in an array. The validation requires that each string is formatted as a telephone number: three groups of digits separated by dashes where the first two groups contain three digits and the third group contains four digits. The search pattern uses the regular expression `^\\d{3}-\\d{3}-\\d{4}$`. For more information, see [Regular Expression Language - Quick Reference](../../standard/base-types/regular-expression-language-quick-reference.md). - -| Pattern | Meaning | -|---------|----------------------------------------| -| `^` | Matches the beginning of the string | -| `\d{3}` | Matches exactly three digit characters | -| `-` | Matches the '-' character | -| `\d{4}` | Matches exactly four digit characters | -| `$` | Matches the end of the string | - -:::code language="csharp" source="./snippets/\strings/SearchStrings.cs" id="Snippet4"::: - -This single search pattern matches many valid strings. Regular expressions are better to search for or validate against a pattern, rather than a single text string. - -## See also - -- [Strings](../programming-guide/strings/index.md) -- -- [.NET regular expressions](../../standard/base-types/regular-expressions.md) -- [Regular expression language - quick reference](../../standard/base-types/regular-expression-language-quick-reference.md) -- [Best practices for using strings in .NET](../../standard/base-types/best-practices-strings.md) diff --git a/docs/csharp/how-to/snippets/strings/ParseStringsUsingSplit.cs b/docs/csharp/how-to/snippets/strings/ParseStringsUsingSplit.cs deleted file mode 100644 index 3e09ff2d24cd4..0000000000000 --- a/docs/csharp/how-to/snippets/strings/ParseStringsUsingSplit.cs +++ /dev/null @@ -1,165 +0,0 @@ -namespace HowToStrings; - -public static class ParseStringsUsingSplit -{ - public static void Examples() - { - Console.WriteLine("Split words"); - Console.WriteLine(); - SplitWords(); - - Console.WriteLine("Enumerate words"); - Console.WriteLine(); - EnumerateWords(); - - Console.WriteLine("Split words with repeated separators"); - Console.WriteLine(); - SplitWordsWithRepeatedSeparators(); - - Console.WriteLine("Split on multiple chars"); - Console.WriteLine(); - SplitOnMultipleChars(); - - Console.WriteLine("Split on multiple chars with gaps"); - Console.WriteLine(); - SplitOnMultipleCharsWithGaps(); - - Console.WriteLine("Split using strings"); - Console.WriteLine(); - SplitUsingStrings(); - - Console.WriteLine("Split into no more than four substrings"); - Console.WriteLine(); - SplitFourTimes(); - - Console.WriteLine("Trim output substrings"); - Console.WriteLine(); - SplitAndTrim(); - } - - private static void SplitWords() - { - // - string phrase = "The quick brown fox jumps over the lazy dog."; - string[] words = phrase.Split(' '); - - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - // - } - - private static void EnumerateWords() - { - // - string phrase = "The quick brown fox jumps over the lazy dog."; - string[] words = phrase.Split(' '); - - for (int i = 0; i < words.Length; i++) - { - Console.WriteLine($"Index {i}: <{words[i]}>"); - } - // - } - - private static void SplitWordsWithRepeatedSeparators() - { - // - string phrase = "The quick brown fox jumps over the lazy dog."; - string[] words = phrase.Split(' '); - - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - // - } - - private static void SplitOnMultipleChars() - { - // - char[] delimiterChars = [' ', ',', '.', ':', '\t']; - - string text = "one\ttwo three:four,five six seven"; - Console.WriteLine($"Original text: '{text}'"); - - string[] words = text.Split(delimiterChars); - Console.WriteLine($"{words.Length} words in text:"); - - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - // - } - - private static void SplitOnMultipleCharsWithGaps() - { - // - char[] delimiterChars = [' ', ',', '.', ':', '\t']; - - string text = "one\ttwo :,five six seven"; - Console.WriteLine($"Original text: '{text}'"); - - string[] words = text.Split(delimiterChars); - Console.WriteLine($"{words.Length} words in text:"); - - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - // - } - - private static void SplitUsingStrings() - { - // - string[] separatingStrings = ["<<", "..."]; - - string text = "one< - } - - private static void SplitFourTimes() - { - // - string phrase = "The quick brown fox jumps over the lazy dog."; - string[] words = phrase.Split(' ', 4, StringSplitOptions.None); - - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - // - } - - private static void SplitAndTrim() - { - // - string numerals = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"; - string[] words = numerals.Split(',', StringSplitOptions.TrimEntries); - - Console.WriteLine("Trimmed entries:"); - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - words = numerals.Split(',', StringSplitOptions.None); - Console.WriteLine("Untrimmed entries:"); - foreach (var word in words) - { - Console.WriteLine($"<{word}>"); - } - // - } -} diff --git a/docs/csharp/how-to/snippets/strings/Program.cs b/docs/csharp/how-to/snippets/strings/Program.cs index 78067d0124cfb..d52b0ec4fe6e6 100644 --- a/docs/csharp/how-to/snippets/strings/Program.cs +++ b/docs/csharp/how-to/snippets/strings/Program.cs @@ -1,14 +1,8 @@ using HowToStrings; -Console.WriteLine("============================ String.Split examples ================================================="); -Console.WriteLine(); -ParseStringsUsingSplit.Examples(); Console.WriteLine("============================ String concatenation examples ================================================="); Console.WriteLine(); Concatenate.Examples(); -Console.WriteLine("============================ String Searching examples ================================================="); -Console.WriteLine(); -SearchStrings.Examples(); Console.WriteLine("============================ Modify string examples ================================================="); Console.WriteLine(); ModifyStrings.Examples(); diff --git a/docs/csharp/how-to/snippets/strings/SearchStrings.cs b/docs/csharp/how-to/snippets/strings/SearchStrings.cs deleted file mode 100644 index ce92fe7d03d33..0000000000000 --- a/docs/csharp/how-to/snippets/strings/SearchStrings.cs +++ /dev/null @@ -1,117 +0,0 @@ -namespace HowToStrings; - -public class SearchStrings -{ - public static void Examples() - { - SearchWithMethods(); - SearchByIndex(); - RegularExpressionsOne(); - RegularExpressionsValidation(); - } - - private static void SearchWithMethods() - { - // - string factMessage = "Extension methods have all the capabilities of regular static methods."; - - // Write the string and include the quotation marks. - Console.WriteLine($"\"{factMessage}\""); - - // Simple comparisons are always case sensitive! - bool containsSearchResult = factMessage.Contains("extension"); - // Raw string literals can work here because the output doesn't begin with " - Console.WriteLine($"""Contains "extension"? {containsSearchResult}"""); - - // For user input and strings that will be displayed to the end user, - // use the StringComparison parameter on methods that have it to specify how to match strings. - bool ignoreCaseSearchResult = factMessage.StartsWith("extension", System.StringComparison.CurrentCultureIgnoreCase); - Console.WriteLine($"""Starts with "extension"? {ignoreCaseSearchResult} (ignoring case)"""); - - bool endsWithSearchResult = factMessage.EndsWith(".", System.StringComparison.CurrentCultureIgnoreCase); - Console.WriteLine($"Ends with '.'? {endsWithSearchResult}"); - // - } - - private static void SearchByIndex() - { - // - string factMessage = "Extension methods have all the capabilities of regular static methods."; - - // Write the string and include the quotation marks. - Console.WriteLine($"\"{factMessage}\""); - - // This search returns the substring between two strings, so - // the first index is moved to the character just after the first string. - int first = factMessage.IndexOf("methods") + "methods".Length; - int last = factMessage.LastIndexOf("methods"); - string str2 = factMessage.Substring(first, last - first); - Console.WriteLine($"""Substring between "methods" and "methods": '{str2}'"""); - // - } - - private static void RegularExpressionsOne() - { - // - string[] sentences = - [ - "Put the water over there.", - "They're quite thirsty.", - "Their water bottles broke." - ]; - - string sPattern = "the(ir)?\\s"; - - foreach (string s in sentences) - { - Console.Write($"{s,24}"); - - if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase)) - { - Console.WriteLine($" (match for '{sPattern}' found)"); - } - else - { - Console.WriteLine(); - } - } - // - } - - private static void RegularExpressionsValidation() - { - // - string[] numbers = - [ - "123-555-0190", - "444-234-22450", - "690-555-0178", - "146-893-232", - "146-555-0122", - "4007-555-0111", - "407-555-0111", - "407-2-5555", - "407-555-8974", - "407-2ab-5555", - "690-555-8148", - "146-893-232-" - ]; - - string sPattern = """^\d{3}-\d{3}-\d{4}$"""; - - foreach (string s in numbers) - { - Console.Write($"{s,14}"); - - if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern)) - { - Console.WriteLine(" - valid"); - } - else - { - Console.WriteLine(" - invalid"); - } - } - // - } -} From 4e09149f93e33c0dd12d1e187d6fe3af4fa2149a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 10:39:45 -0400 Subject: [PATCH 06/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../language-reference/builtin-types/string-operations.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/csharp/language-reference/builtin-types/string-operations.md b/docs/csharp/language-reference/builtin-types/string-operations.md index 4c44307f2a22f..126696a6a2e94 100644 --- a/docs/csharp/language-reference/builtin-types/string-operations.md +++ b/docs/csharp/language-reference/builtin-types/string-operations.md @@ -1,7 +1,9 @@ --- title: "String operations: pattern matching, performance, and span-based search" description: "Learn how to apply regular-expression patterns to strings, search strings with ReadOnlySpan, and pick the right StringComparison for performance." +ms.topic: reference ms.date: 05/21/2026 +ai-usage: ai-assisted --- # String operations: pattern matching, performance, and span-based search From efff3527cd426e8238da88b5aec38029da3f7ef7 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 11:01:31 -0400 Subject: [PATCH 07/10] Fix warnings. --- .../fundamentals/strings/interpolation.md | 14 ++++---- docs/csharp/fundamentals/strings/search.md | 10 +++--- .../strings/snippets/interpolation/Program.cs | 12 +++---- .../strings/snippets/search/Program.cs | 8 ++--- .../strings/snippets/split/Program.cs | 32 +++++++++---------- docs/csharp/fundamentals/strings/split.md | 22 ++++++------- .../snippets/string-operations/Program.cs | 12 +++---- .../builtin-types/string-operations.md | 6 ++-- 8 files changed, 58 insertions(+), 58 deletions(-) diff --git a/docs/csharp/fundamentals/strings/interpolation.md b/docs/csharp/fundamentals/strings/interpolation.md index 341cb2cba1acc..71befede657b6 100644 --- a/docs/csharp/fundamentals/strings/interpolation.md +++ b/docs/csharp/fundamentals/strings/interpolation.md @@ -17,13 +17,13 @@ ai-usage: ai-assisted :::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 and support the full [composite formatting](../../standard/base-types/composite-formatting.md) feature set. +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 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` 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): +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 {:} @@ -31,7 +31,7 @@ To control how an expression result is formatted, follow the expression with a c The following example formats a date and a numeric value: -:::code language="csharp" source="snippets/interpolation/Program.cs" id="format-string"::: +:::code language="csharp" source="snippets/interpolation/Program.cs" id="FormatString"::: ## Set the field width and alignment @@ -49,7 +49,7 @@ When you need both alignment and a format string, put alignment first: {,:} ``` -:::code language="csharp" source="snippets/interpolation/Program.cs" id="alignment-and-format"::: +:::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. @@ -80,7 +80,7 @@ For better readability, you can break long interpolation expressions across mult 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="constant-interpolated"::: +:::code language="csharp" source="snippets/interpolation/Program.cs" id="ConstantInterpolated"::: ## Format with a specific culture @@ -96,7 +96,7 @@ For invariant output (logs, file formats, machine-readable data), pass - diff --git a/docs/csharp/fundamentals/strings/search.md b/docs/csharp/fundamentals/strings/search.md index ca12d30f363c6..0c0c50f5c2746 100644 --- a/docs/csharp/fundamentals/strings/search.md +++ b/docs/csharp/fundamentals/strings/search.md @@ -18,7 +18,7 @@ The class includes methods that answer two everyday questio - *Does this string contain that text?* — use , , or . - *Where does that text occur?* — use or . -For pattern matching (regular expressions), span-based search over `ReadOnlySpan`, 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). +For pattern matching (regular expressions), span-based search over `ReadOnlySpan`, 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 @@ -30,13 +30,13 @@ These methods default to **case-sensitive, ordinal** comparison. To accept user 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="contains-char"::: +:::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="index-of"::: +:::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. @@ -57,7 +57,7 @@ Ordinal comparison is the fastest option and the right default for anything that ## 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) +- [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md) +- [Comparing strings](../../../standard/base-types/comparing.md) - - diff --git a/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs b/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs index 2f18baf2c3623..491587aaa08a5 100644 --- a/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs +++ b/docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs @@ -42,11 +42,11 @@ private static void General() private static void 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. - // + // } private static void Alignment() @@ -77,7 +77,7 @@ private static void Alignment() private static void AlignmentAndFormat() { - // + // const int NameAlignment = -9; const int ValueAlignment = 7; double a = 3; @@ -90,7 +90,7 @@ private static void AlignmentAndFormat() // => |Arithmetic| 3.500| // => |Geometric| 3.464| // => |Harmonic | 3.429| - // + // } private static void Escapes() @@ -137,12 +137,12 @@ private static void Newlines() private static void ConstantInterpolated() { - // + // const string Audience = "world"; const string Greeting = $"Hello, {Audience}!"; Console.WriteLine(Greeting); // => Hello, world! - // + // } private static void Culture() diff --git a/docs/csharp/fundamentals/strings/snippets/search/Program.cs b/docs/csharp/fundamentals/strings/snippets/search/Program.cs index b359230ccf359..0deacbd654d1e 100644 --- a/docs/csharp/fundamentals/strings/snippets/search/Program.cs +++ b/docs/csharp/fundamentals/strings/snippets/search/Program.cs @@ -38,17 +38,17 @@ private static void Contains() private static void ContainsChar() { - // + // string path = "/usr/local/bin"; bool hasSlash = path.Contains('/'); Console.WriteLine($"Path contains '/': {hasSlash}"); // => Path contains '/': True - // + // } private static void IndexOfExample() { - // + // string factMessage = "Extension methods have all the capabilities of regular static methods."; Console.WriteLine($"\"{factMessage}\""); @@ -60,6 +60,6 @@ private static void IndexOfExample() Console.WriteLine($"""Substring between "methods" and "methods": '{between}'"""); // => "Extension methods have all the capabilities of regular static methods." // => Substring between "methods" and "methods": ' have all the capabilities of regular static ' - // + // } } diff --git a/docs/csharp/fundamentals/strings/snippets/split/Program.cs b/docs/csharp/fundamentals/strings/snippets/split/Program.cs index fb825d5673b0b..4e1c1c53eaee6 100644 --- a/docs/csharp/fundamentals/strings/snippets/split/Program.cs +++ b/docs/csharp/fundamentals/strings/snippets/split/Program.cs @@ -23,7 +23,7 @@ public static void Main() private static void SplitWords() { - // + // string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' '); @@ -40,12 +40,12 @@ private static void SplitWords() // => // => // => - // + // } private static void IndexWords() { - // + // string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' '); @@ -57,12 +57,12 @@ private static void IndexWords() // => Index 1: // => Index 2: // => ... - // + // } private static void RepeatedSeparators() { - // + // string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' '); @@ -79,12 +79,12 @@ private static void RepeatedSeparators() // => <> // => // => ... - // + // } private static void MultiChar() { - // + // char[] delimiters = [' ', ',', '.', ':', '\t']; string text = "one\ttwo three:four,five six seven"; @@ -105,12 +105,12 @@ private static void MultiChar() // => // => // => - // + // } private static void MultiCharGaps() { - // + // char[] delimiters = [' ', ',', '.', ':', '\t']; string text = "one\ttwo :,five six seven"; @@ -124,12 +124,12 @@ private static void MultiCharGaps() Console.WriteLine($"<{word}>"); } // Adjacent delimiters produce empty entries — use StringSplitOptions.RemoveEmptyEntries to drop them. - // + // } private static void StringSeparators() { - // + // string[] separators = ["<<", "..."]; string text = "one< one // => two // => three + // } private static void LimitCount() { - // + // string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' ', 4, StringSplitOptions.None); @@ -163,12 +163,12 @@ private static void LimitCount() // => // => // => - // + // } private static void TrimEntries() { - // + // string numerals = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"; string[] trimmed = numerals.Split(',', StringSplitOptions.TrimEntries); @@ -186,6 +186,6 @@ private static void TrimEntries() } // => Trimmed entries: <1> <2> ... <10> // => Untrimmed entries: <1> < 2> ... < 10> - // + // } } diff --git a/docs/csharp/fundamentals/strings/split.md b/docs/csharp/fundamentals/strings/split.md index b88ef590dade2..84fd4f88705ea 100644 --- a/docs/csharp/fundamentals/strings/split.md +++ b/docs/csharp/fundamentals/strings/split.md @@ -28,15 +28,15 @@ The rest of this article walks through the common combinations. To split a phrase on whitespace, pass `' '` as the separator: -:::code language="csharp" source="snippets/split/Program.cs" id="split-words"::: +:::code language="csharp" source="snippets/split/Program.cs" id="SplitWords"::: The returned array is zero-indexed, so you can iterate over it with `for` to recover the position of each word: -:::code language="csharp" source="snippets/split/Program.cs" id="index-words"::: +:::code language="csharp" source="snippets/split/Program.cs" id="IndexWords"::: If the input contains runs of the separator character, `Split` produces empty entries — one for each "gap" between consecutive separators: -:::code language="csharp" source="snippets/split/Program.cs" id="repeated-separators"::: +:::code language="csharp" source="snippets/split/Program.cs" id="RepeatedSeparators"::: Pass `StringSplitOptions.RemoveEmptyEntries` to drop those empty entries (shown later). @@ -44,23 +44,23 @@ Pass `StringSplitOptions.RemoveEmptyEntries` to drop those empty entries (shown When more than one character can act as a separator, pass them as an array. The following example treats spaces, commas, periods, colons, and tabs all as word boundaries: -:::code language="csharp" source="snippets/split/Program.cs" id="multi-char"::: +:::code language="csharp" source="snippets/split/Program.cs" id="MultiChar"::: Adjacent separators still produce empty entries: -:::code language="csharp" source="snippets/split/Program.cs" id="multi-char-gaps"::: +:::code language="csharp" source="snippets/split/Program.cs" id="MultiCharGaps"::: -## Split on multi-character separators +## Split on MultiCharacter separators -To split on whole-word or multi-character separators, pass an array of strings together with a value: +To split on whole-word or MultiCharacter separators, pass an array of strings together with a value: -:::code language="csharp" source="snippets/split/Program.cs" id="string-separators"::: +:::code language="csharp" source="snippets/split/Program.cs" id="StringSeparators"::: ## Limit how many substrings you get back Pass a `count` argument to cap the number of results. The final entry holds everything that's left, including any remaining separators: -:::code language="csharp" source="snippets/split/Program.cs" id="limit-count"::: +:::code language="csharp" source="snippets/split/Program.cs" id="LimitCount"::: This pattern is handy for `key=value` pairs and other formats where only the first separator is meaningful. @@ -68,7 +68,7 @@ This pattern is handy for `key=value` pairs and other formats where only the fir `StringSplitOptions.TrimEntries` strips leading and trailing whitespace from every returned substring. You can combine it with `RemoveEmptyEntries` for typical CSV-style cleanup: -:::code language="csharp" source="snippets/split/Program.cs" id="trim-entries"::: +:::code language="csharp" source="snippets/split/Program.cs" id="TrimEntries"::: ## When to reach for a regular expression @@ -79,4 +79,4 @@ This pattern is handy for `key=value` pairs and other formats where only the fir - - - [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md) -- [Extract elements from a string](../../standard/base-types/divide-up-strings.md) +- [Extract elements from a string](../../../standard/base-types/divide-up-strings.md) diff --git a/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs b/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs index 80c99e773e04f..ca166b61bad9c 100644 --- a/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs +++ b/docs/csharp/language-reference/builtin-types/snippets/string-operations/Program.cs @@ -15,7 +15,7 @@ public static void Main() private static void RegexPattern() { - // + // string[] sentences = [ "Put the water over there.", @@ -38,12 +38,12 @@ private static void RegexPattern() Console.WriteLine(); } } - // + // } private static void RegexValidate() { - // + // string[] numbers = [ "123-555-0190", @@ -67,12 +67,12 @@ private static void RegexValidate() Console.Write($"{s,14}"); Console.WriteLine(Regex.IsMatch(s, pattern) ? " - valid" : " - invalid"); } - // + // } private static void SpanSearch() { - // + // ReadOnlySpan input = "key1=alpha;key2=beta;key3=gamma".AsSpan(); ReadOnlySpan needle = "key2=".AsSpan(); @@ -85,6 +85,6 @@ private static void SpanSearch() Console.WriteLine($"key2 = {value}"); } // => key2 = beta - // + // } } diff --git a/docs/csharp/language-reference/builtin-types/string-operations.md b/docs/csharp/language-reference/builtin-types/string-operations.md index 126696a6a2e94..ff84a057c5aef 100644 --- a/docs/csharp/language-reference/builtin-types/string-operations.md +++ b/docs/csharp/language-reference/builtin-types/string-operations.md @@ -22,7 +22,7 @@ The following example searches each sentence for the word *the* or *their*, case | `(ir)?` | match 0 or 1 occurrence of `ir` | | `\s` | match a whitespace character | -:::code language="csharp" source="snippets/string-operations/Program.cs" id="regex-pattern"::: +:::code language="csharp" source="snippets/string-operations/Program.cs" id="RegexPattern"::: ## Validate strings against a pattern @@ -36,7 +36,7 @@ To check whether an entire input matches a shape, anchor the pattern with `^` an | `\d{4}` | match exactly four digit characters | | `$` | match the end of the string | -:::code language="csharp" source="snippets/string-operations/Program.cs" id="regex-validate"::: +:::code language="csharp" source="snippets/string-operations/Program.cs" id="RegexValidate"::: For the full pattern syntax, see [Regular expression language — quick reference](../../../standard/base-types/regular-expression-language-quick-reference.md). @@ -48,7 +48,7 @@ For the full pattern syntax, see [Regular expression language — quick referenc When you parse large inputs or run a search on a hot path, the per-call allocations of `string.Substring` and `string.Split` can dominate. `ReadOnlySpan` gives you a view over an existing string (or array, or stack buffer) without copying, and provides span-based equivalents of the common `string` methods, including : -:::code language="csharp" source="snippets/string-operations/Program.cs" id="span-search"::: +:::code language="csharp" source="snippets/string-operations/Program.cs" id="SpanSearch"::: Span-based search avoids allocations because the slices (`input[start..]`, `rest[..end]`) are simply windows over the original characters. The same approach scales to parsing key-value lists, headers, and other delimited text without ever calling `Substring`. From c24faa685e880e45af64fc041871250e2e087ca9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 15:06:55 -0400 Subject: [PATCH 08/10] content review Review the major changes and edit / content review. --- .../fundamentals/strings/interpolation.md | 8 ++++---- docs/csharp/fundamentals/strings/search.md | 18 ++++++++---------- .../strings/snippets/split/Program.cs | 9 ++++++++- docs/csharp/fundamentals/strings/split.md | 14 +++++++------- .../builtin-types/string-operations.md | 18 +++++++++--------- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/docs/csharp/fundamentals/strings/interpolation.md b/docs/csharp/fundamentals/strings/interpolation.md index 71befede657b6..aa0da47d8e9c5 100644 --- a/docs/csharp/fundamentals/strings/interpolation.md +++ b/docs/csharp/fundamentals/strings/interpolation.md @@ -13,7 +13,7 @@ ai-usage: ai-assisted > > **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 expression results directly in a string literal by prefixing the literal with `$`: +*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"::: @@ -35,7 +35,7 @@ The following example formats a date and a numeric value: ## 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; negative widths left-align it: +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 {,} @@ -63,13 +63,13 @@ For paths and other strings that contain backslashes, prefer an [interpolated ra ## Use a conditional expression -The colon has special meaning inside an interpolation expression, so wrap a [ternary conditional operator](../../language-reference/operators/conditional-operator.md) in parentheses: +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, you can break long interpolation expressions across multiple lines. The example below uses an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) so the expression body can wrap freely: +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"::: diff --git a/docs/csharp/fundamentals/strings/search.md b/docs/csharp/fundamentals/strings/search.md index 0c0c50f5c2746..9cee8f76bb525 100644 --- a/docs/csharp/fundamentals/strings/search.md +++ b/docs/csharp/fundamentals/strings/search.md @@ -11,14 +11,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. > -> **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 C# searches default to **ordinal, case-sensitive** comparison; for user-facing searches you might want to pass a value. +> **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 value. The class includes methods that answer two everyday questions: - *Does this string contain that text?* — use , , or . - *Where does that text occur?* — use or . -For pattern matching (regular expressions), span-based search over `ReadOnlySpan`, 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). +For regular expressions, span-based search over `ReadOnlySpan`, 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 @@ -44,15 +44,13 @@ When you need every occurrence rather than the first or last, iterate by passing Most search overloads accept an optional value. Pick it based on the kind of data you're searching: -| Scenario | Recommended value | -|-------------------------------------------------------------------------|-----------------------------------------------------------------| -| Identifiers, file paths, protocol tokens, anything machine-defined | | -| Same as above, but you want case insensitivity | | -| User-visible text where the current locale's rules should apply | | -| Same as above, ignoring case | | -| Persisted data that must compare the same on every machine and culture | (rarely needed) | +- If you're searching identifiers, file paths, protocol tokens, or anything else machine-defined, use . +- If you're searching the same kind of machine-defined data but want case insensitivity, use . +- If you're searching user-visible text where the current locale's rules should apply, use . +- If you're searching that same user-visible text and want to ignore case, use . +- If you're searching persisted data that must compare the same on every machine and culture, use (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` — so reserve it for searches that real users perform against real prose. +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 diff --git a/docs/csharp/fundamentals/strings/snippets/split/Program.cs b/docs/csharp/fundamentals/strings/snippets/split/Program.cs index 4e1c1c53eaee6..0b0f4d8f715b6 100644 --- a/docs/csharp/fundamentals/strings/snippets/split/Program.cs +++ b/docs/csharp/fundamentals/strings/snippets/split/Program.cs @@ -123,7 +123,14 @@ private static void MultiCharGaps() { Console.WriteLine($"<{word}>"); } - // Adjacent delimiters produce empty entries — use StringSplitOptions.RemoveEmptyEntries to drop them. + // => 7 words in text: + // => + // => + // => <> + // => <> + // => + // => + // => // } diff --git a/docs/csharp/fundamentals/strings/split.md b/docs/csharp/fundamentals/strings/split.md index 84fd4f88705ea..515597186ae99 100644 --- a/docs/csharp/fundamentals/strings/split.md +++ b/docs/csharp/fundamentals/strings/split.md @@ -11,7 +11,7 @@ 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 another language?** `string.Split` is C#'s counterpart to Java's `String.split` and JavaScript's `String.prototype.split`. Unlike those languages, C# returns an array (`string[]`), not a list, and the separator argument is a character or string — never a regular expression. For pattern-based splitting, see . +> **Coming from another language?** `string.Split` is C#'s counterpart to Java's `String.split` and JavaScript's `String.prototype.split`. Unlike those languages, C# returns an array (`string[]`), not a list, and the separator argument is a character or string, not a regular expression. For pattern-based splitting, see . The method breaks a string into an array of substrings using one or more separators. It's the simplest way to parse delimited text such as words, CSV-style values, or protocol tokens. @@ -30,11 +30,11 @@ To split a phrase on whitespace, pass `' '` as the separator: :::code language="csharp" source="snippets/split/Program.cs" id="SplitWords"::: -The returned array is zero-indexed, so you can iterate over it with `for` to recover the position of each word: +Iterate the returned array with `for` to recover the position of each word: :::code language="csharp" source="snippets/split/Program.cs" id="IndexWords"::: -If the input contains runs of the separator character, `Split` produces empty entries — one for each "gap" between consecutive separators: +If the input contains runs of the separator character, `Split` produces empty entries, one for each "gap" between consecutive separators: :::code language="csharp" source="snippets/split/Program.cs" id="RepeatedSeparators"::: @@ -50,9 +50,9 @@ Adjacent separators still produce empty entries: :::code language="csharp" source="snippets/split/Program.cs" id="MultiCharGaps"::: -## Split on MultiCharacter separators +## Split on multicharacter separators -To split on whole-word or MultiCharacter separators, pass an array of strings together with a value: +To split on whole-word or multicharacter separators, pass an array of strings together with a value: :::code language="csharp" source="snippets/split/Program.cs" id="StringSeparators"::: @@ -70,9 +70,9 @@ This pattern is handy for `key=value` pairs and other formats where only the fir :::code language="csharp" source="snippets/split/Program.cs" id="TrimEntries"::: -## When to reach for a regular expression +## Use regular expressions -`Split` works well for fixed character or string delimiters. For pattern-based splitting — for example, "split on any run of whitespace" or "split on either `;` or `, `" — use . See [String operations](../../language-reference/builtin-types/string-operations.md) for an introduction to regular expressions on strings. +`Split` works well for fixed character or string delimiters. For pattern-based splitting, use . See [String operations](../../language-reference/builtin-types/string-operations.md) for an introduction to regular expressions on strings. ## See also diff --git a/docs/csharp/language-reference/builtin-types/string-operations.md b/docs/csharp/language-reference/builtin-types/string-operations.md index ff84a057c5aef..5424e2977e85a 100644 --- a/docs/csharp/language-reference/builtin-types/string-operations.md +++ b/docs/csharp/language-reference/builtin-types/string-operations.md @@ -8,9 +8,9 @@ ai-usage: ai-assisted # String operations: pattern matching, performance, and span-based search -This article covers string operations that go beyond the everyday `Contains`/`IndexOf`/`Split` methods covered in the Fundamentals [Search strings](../../fundamentals/strings/search.md) and [Split strings into substrings](../../fundamentals/strings/split.md) articles. It focuses on three areas: regular-expression pattern matching, allocation-free search over , and performance considerations for `string` comparison. +This article covers string operations that go beyond the everyday `Contains`, `IndexOf`, and `Split` methods covered in the Fundamentals [Search strings](../../fundamentals/strings/search.md) and [Split strings into substrings](../../fundamentals/strings/split.md) articles. It focuses on three areas: regular-expression pattern matching, allocation-free search over , and performance considerations for `string` comparison. -## Find specific text using regular expressions +## Find specific text by using regular expressions The class searches strings for patterns rather than fixed substrings. The static method takes the input string, a pattern, and optional flags. @@ -26,7 +26,7 @@ The following example searches each sentence for the word *the* or *their*, case ## Validate strings against a pattern -To check whether an entire input matches a shape, anchor the pattern with `^` and `$`. The following example validates that each string is a US-style telephone number — three digits, three digits, four digits, separated by dashes: +To check whether an entire input matches a shape, anchor the pattern with `^` and `$`. The following example validates that each string is a US-style telephone number: three digits, three digits, four digits, separated by dashes: | Pattern | Meaning | |---------|----------------------------------------| @@ -38,11 +38,11 @@ To check whether an entire input matches a shape, anchor the pattern with `^` an :::code language="csharp" source="snippets/string-operations/Program.cs" id="RegexValidate"::: -For the full pattern syntax, see [Regular expression language — quick reference](../../../standard/base-types/regular-expression-language-quick-reference.md). +For the full pattern syntax, see [Regular expression language - quick reference](../../../standard/base-types/regular-expression-language-quick-reference.md). ## Choose between `string` methods and regular expressions -`string` methods and `Regex` solve overlapping problems. Prefer `string` methods when the text you're searching for is a literal value, a known prefix or suffix, or a fixed delimiter — they're simpler to read and faster, because they don't pay the cost of compiling and executing a pattern. Reach for `Regex` when the search target is a *shape* — alternations, optional groups, repeated character classes, or anchored validation. As a rule of thumb, if you can write the search as one or two `string.Contains` / `StartsWith` / `IndexOf` calls, do so. +`string` methods and `Regex` solve overlapping problems. Prefer `string` methods when the text you're searching for is a literal value, a known prefix or suffix, or a fixed delimiter. They're simpler to read and faster, because they don't pay the cost of compiling and executing a pattern. Reach for `Regex` when the search target is a *shape*, such as alternations, optional groups, repeated character classes, or anchored validation. As a rule of thumb, if you can write the search as one or two `string.Contains` / `StartsWith` / `IndexOf` calls, do so. ## Search using `ReadOnlySpan` @@ -54,12 +54,12 @@ Span-based search avoids allocations because the slices (`input[start..]`, `rest ## Performance considerations for `StringComparison` -Most `string` instance methods have overloads that accept a value. The default for methods such as and is **ordinal**, but the default for is **current culture**. The difference matters in two ways: +Most `string` instance methods have overloads that accept a value. Methods such as default to **ordinal**, but and default to **current culture**. This difference matters in two ways: -- **Speed.** Ordinal comparison is a byte-for-byte test that runs in tight, vectorized loops. Culture-aware comparison consults a sort table, walks combining characters, and applies locale-specific rules — it can be an order of magnitude slower for the same input. -- **Correctness.** Culture-aware comparison can fold characters that you don't expect (Turkish `i`/`I`, German `ß` to `ss`, ligatures), which is the right behavior for sorting names a user sees but the wrong behavior for parsing identifiers, paths, or protocol tokens. +- **Speed.** Ordinal comparison is a byte-for-byte test that runs in tight, vectorized loops. Culture-aware comparison consults a sort table, walks combining characters, and applies locale-specific rules. For the same input, it can be an order of magnitude slower. +- **Correctness.** Culture-aware comparison can fold characters that you don't expect (Turkish `i`/`I`, German `ß` to `ss`, ligatures). This behavior is right for sorting names a user sees but wrong for parsing identifiers, paths, or protocol tokens. -For machine-defined text — file names, URLs, HTTP headers, identifiers, configuration keys — pass or explicitly. Reserve culture-aware values for natural-language text shown to users. For comprehensive guidance, see [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md). +For machine-defined text, such as file names, URLs, HTTP headers, identifiers, configuration keys, pass or explicitly. Reserve culture-aware values for natural-language text shown to users. For comprehensive guidance, see [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md). ## See also From 81de07f6c64f2f266f4fa6a02ff182793e6f2697 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 15:30:24 -0400 Subject: [PATCH 09/10] Fix warnings. --- docs/csharp/how-to/index.md | 4 ++-- docs/csharp/how-to/modify-string-contents.md | 2 +- docs/csharp/programming-guide/strings/index.md | 6 +++--- docs/standard/base-types/divide-up-strings.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/csharp/how-to/index.md b/docs/csharp/how-to/index.md index 67bca16daef6d..1a65f1b3b7a29 100644 --- a/docs/csharp/how-to/index.md +++ b/docs/csharp/how-to/index.md @@ -44,9 +44,9 @@ Strings are the fundamental data type used to display or manipulate text. These - [Compare strings](compare-strings.md). - [Modify the contents of a string](modify-string-contents.md). - [Determine if a string represents a number](../programming-guide/strings/how-to-determine-whether-a-string-represents-a-numeric-value.md). -- [Use `String.Split` to separate strings](parse-strings-using-split.md). +- [Use `String.Split` to separate strings](../fundamentals/strings/split.md). - [Combine multiple strings into one](concatenate-multiple-strings.md). -- [Search for text in a string](search-strings.md). +- [Search for text in a string](../fundamentals/strings/search.md). ## Convert between types diff --git a/docs/csharp/how-to/modify-string-contents.md b/docs/csharp/how-to/modify-string-contents.md index d6136bcc7d2a3..10dbef31ae5c6 100644 --- a/docs/csharp/how-to/modify-string-contents.md +++ b/docs/csharp/how-to/modify-string-contents.md @@ -41,7 +41,7 @@ You can remove text from a string using the class to find a pattern in a source string and replace it with proper capitalization. The method takes a function that provides the logic of the replacement as one of its arguments. In this example, that function, `LocalReplaceMatchCase` is a **local function** declared inside the sample method. `LocalReplaceMatchCase` uses the class to build the replacement string with proper capitalization. -Regular expressions are most useful for searching and replacing text that follows a pattern, rather than known text. For more information, see [How to search strings](search-strings.md). The search pattern, "the\s" searches for the word "the" followed by a white-space character. That part of the pattern ensures that it doesn't match "there" in the source string. For more information on regular expression language elements, see [Regular Expression Language - Quick Reference](../../standard/base-types/regular-expression-language-quick-reference.md). +Regular expressions are most useful for searching and replacing text that follows a pattern, rather than known text. For more information, see [Search strings in C#](../fundamentals/strings/search.md). The search pattern, "the\s" searches for the word "the" followed by a white-space character. That part of the pattern ensures that it doesn't match "there" in the source string. For more information on regular expression language elements, see [Regular Expression Language - Quick Reference](../../standard/base-types/regular-expression-language-quick-reference.md). :::code language="csharp" source="./snippets/strings/ModifyStrings.cs" id="Snippet5"::: diff --git a/docs/csharp/programming-guide/strings/index.md b/docs/csharp/programming-guide/strings/index.md index 20d4e754326e8..ef1fc31b7edd7 100644 --- a/docs/csharp/programming-guide/strings/index.md +++ b/docs/csharp/programming-guide/strings/index.md @@ -137,7 +137,7 @@ For more information, see [Composite formatting in .NET](../../../standard/base- ## Substrings -A substring is any sequence of characters that is contained in a string. Use the method to create a new string from a part of the original string. You can search for one or more occurrences of a substring by using the method. Use the method to replace all occurrences of a specified substring with a new string. Like the method, actually returns a new string and doesn't modify the original string. For more information, see [How to search strings](../../how-to/search-strings.md) and [How to modify string contents](../../how-to/modify-string-contents.md). +A substring is any sequence of characters that is contained in a string. Use the method to create a new string from a part of the original string. You can search for one or more occurrences of a substring by using the method. Use the method to replace all occurrences of a specified substring with a new string. Like the method, actually returns a new string and doesn't modify the original string. For more information, see [Search strings in C#](../../fundamentals/strings/search.md) and [How to modify string contents](../../how-to/modify-string-contents.md). :::code language="csharp" source="./snippets/StringCharacters.cs" id="Substrings"::: @@ -184,8 +184,8 @@ Because the type implements method to parse strings. -- [How to search strings](../../how-to/search-strings.md): Explains how to use search for specific text or patterns in strings. +- [Split strings into substrings in C#](../../fundamentals/strings/split.md): Contains code examples that illustrate how to use the method to parse strings. +- [Search strings in C#](../../fundamentals/strings/search.md): Explains how to search for specific text or patterns in strings. - [How to determine whether a string represents a numeric value](./how-to-determine-whether-a-string-represents-a-numeric-value.md): Shows how to safely parse a string to see whether it has a valid numeric value. - [String interpolation](../../language-reference/tokens/interpolated.md): Describes the string interpolation feature that provides a convenient syntax to format strings. - [Using the StringBuilder class in .NET](../../../standard/base-types/stringbuilder.md) diff --git a/docs/standard/base-types/divide-up-strings.md b/docs/standard/base-types/divide-up-strings.md index 295edaede453e..44c7c53eaa68f 100644 --- a/docs/standard/base-types/divide-up-strings.md +++ b/docs/standard/base-types/divide-up-strings.md @@ -132,5 +132,5 @@ The next example uses the index-from-end operator to remove a file extension (th ## See also - [.NET regular expressions](regular-expressions.md) -- [How to parse strings using String.Split in C#](../../csharp/how-to/parse-strings-using-split.md) +- [Split strings into substrings in C#](../../csharp/fundamentals/strings/split.md) - [Indices and ranges (C# guide)](../../csharp/tutorials/ranges-indexes.md) From c190eb308a15a70873fe838219f8949a8ae22f19 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 26 May 2026 11:00:30 -0400 Subject: [PATCH 10/10] fix warnings --- docs/csharp/tutorials/string-interpolation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/csharp/tutorials/string-interpolation.md b/docs/csharp/tutorials/string-interpolation.md index fd94d98d9a8c3..ab8e8c082d485 100644 --- a/docs/csharp/tutorials/string-interpolation.md +++ b/docs/csharp/tutorials/string-interpolation.md @@ -1,11 +1,12 @@ --- -title: String interpolation +title: Learn String interpolation description: Learn how to include formatted expression results in a result string in C# with string interpolation. author: pkulikov ms.subservice: fundamentals ms.date: 11/18/2025 +ms.topic: tutorial --- -# String interpolation in C\# +# Tutorial: Learn String interpolation in C\# This tutorial shows you how to use [string interpolation](../language-reference/tokens/interpolated.md) to format and include expression results in a result string. The examples assume that you're familiar with basic C# concepts and .NET type formatting. For more information about formatting types in .NET, see [Formatting types in .NET](../../standard/base-types/formatting-types.md).