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/fundamentals/strings/interpolation.md b/docs/csharp/fundamentals/strings/interpolation.md new file mode 100644 index 0000000000000..aa0da47d8e9c5 --- /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 expressions directly in a string literal by prefixing the literal with `$`: + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="general"::: + +Each `{ }` is an *interpolation expression*. C# evaluates the expression, converts the result to a string by calling its `ToString` method, and substitutes the text into the result. If the expression evaluates to `null`, C# substitutes an empty string. Interpolated strings are a more readable alternative to 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="FormatString"::: + +## Set the field width and alignment + +To produce aligned output, follow the expression with a comma and a minimum field width. Positive widths right-align the value, and negative widths left-align it: + +```csharp +{,} +``` + +:::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="AlignmentAndFormat"::: + +If the formatted value is longer than the requested width, C# ignores the width and emits the full value. + +## Escape braces and use escape sequences + +Interpolated strings support the same escape sequences as ordinary string literals. To include a literal `{` or `}` character in the result, double it (`{{` or `}}`). + +For paths and other strings that contain backslashes, prefer an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) (`$"""..."""`) over the older verbatim form (`$@"..."`). Raw string literals don't process escape sequences, so backslashes appear as-is: + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="escapes"::: + +## Use a conditional expression + +The colon has special meaning inside an interpolation expression, so wrap a [conditional operator](../../language-reference/operators/conditional-operator.md) in parentheses: + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="conditional"::: + +## Span an expression across multiple lines + +For better readability, break long interpolation expressions across multiple lines. The following example uses an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) so the expression body can wrap freely: + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="newlines"::: + +> [!NOTE] +> Multi-line interpolation expressions are available since C# 11. + +## Build constant strings + +When every interpolated expression is itself a constant `string`, the whole interpolated string is also a `const`. That makes it usable for attribute arguments, `switch` patterns, and other contexts that require compile-time constants: + +:::code language="csharp" source="snippets/interpolation/Program.cs" id="ConstantInterpolated"::: + +## Format with a specific culture + +By default, an interpolated string formats values using , 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..9cee8f76bb525 --- /dev/null +++ b/docs/csharp/fundamentals/strings/search.md @@ -0,0 +1,61 @@ +--- +title: "Search strings in C#" +description: Learn how to find text within strings in C# with the Contains, StartsWith, EndsWith, IndexOf, and LastIndexOf methods, and how to choose the right StringComparison. +ms.date: 05/21/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Search strings in C\# + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. +> +> **Coming from another language?** C# `string` methods such as `Contains`, `StartsWith`, and `IndexOf` parallel methods in Java's `String` and JavaScript's `String.prototype`. The key difference is that some C# searches default to **ordinal, case-sensitive** comparison. Others default to the current culture's semantics. For user-facing searches, you might want to pass a 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 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="ContainsChar"::: + +## Locate the position of text + +`IndexOf` returns the zero-based index of the first occurrence of a substring (or character), and `LastIndexOf` returns the index of the last occurrence. Both return `-1` when the search text isn't present. Combine them to extract the text between two markers: + +:::code language="csharp" source="snippets/search/Program.cs" id="IndexOf"::: + +When you need every occurrence rather than the first or last, iterate by passing the previous result plus one as the `startIndex` argument, or switch to a regular expression. + +## Choose the right comparison + +Most search overloads accept an optional value. Pick it based on the kind of data you're searching: + +- If you're searching identifiers, file paths, protocol tokens, or anything else machine-defined, use . +- 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`.Reserve it for searches that users perform against prose. + +## See also + +- [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md) +- [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md) +- [Comparing strings](../../../standard/base-types/comparing.md) +- +- 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..491587aaa08a5 --- /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..0deacbd654d1e --- /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..0b0f4d8f715b6 --- /dev/null +++ b/docs/csharp/fundamentals/strings/snippets/split/Program.cs @@ -0,0 +1,198 @@ +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}>"); + } + // => 7 words in text: + // => + // => + // => <> + // => <> + // => + // => + // => + // + } + + 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/fundamentals/strings/split.md b/docs/csharp/fundamentals/strings/split.md new file mode 100644 index 0000000000000..515597186ae99 --- /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, 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. + +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="SplitWords"::: + +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: + +:::code language="csharp" source="snippets/split/Program.cs" id="RepeatedSeparators"::: + +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="MultiChar"::: + +Adjacent separators still produce empty entries: + +:::code language="csharp" source="snippets/split/Program.cs" id="MultiCharGaps"::: + +## Split on multicharacter separators + +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"::: + +## 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="LimitCount"::: + +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="TrimEntries"::: + +## Use regular expressions + +`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 + +- +- +- [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) 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/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"); - } - } - // - } -} 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..ca166b61bad9c --- /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 + + 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..5424e2977e85a --- /dev/null +++ b/docs/csharp/language-reference/builtin-types/string-operations.md @@ -0,0 +1,72 @@ +--- +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 + +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 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. + +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="RegexPattern"::: + +## 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="RegexValidate"::: + +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*, 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` + +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="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`. + +## Performance considerations for `StringComparison` + +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. 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, 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 + +- [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) +- +- +- 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/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/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 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). 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)