Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .openpublishing.redirection.csharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,10 +659,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"
Expand Down
102 changes: 102 additions & 0 deletions docs/csharp/fundamentals/strings/interpolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: "String interpolation in C#"
description: Learn how to build result strings in C# with string interpolation by embedding formatted expression results directly in a string literal.
ms.date: 05/21/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# String interpolation in C\#

> [!TIP]
> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first.
>
> **Coming from another language?** String interpolation in C# works much like template literals in JavaScript (`` `${x}` ``) or f-strings in Python (`f"{x}"`). The expression inside `{}` can be any valid C# expression, and you can add format and alignment specifiers without leaving the string.

*String interpolation* lets you embed expressions directly in a string literal by prefixing the literal with `$`:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="general":::

Each `{ }` is an *interpolation expression*. C# evaluates the expression, converts the result to a string by calling its `ToString` method, and substitutes the text into the result. If the expression evaluates to `null`, C# substitutes an empty string. Interpolated strings are a more readable alternative to <xref:System.String.Format*?displayProperty=nameWithType> and support the full [composite formatting](../../../standard/base-types/composite-formatting.md) feature set.

For the language-reference treatment of the syntax and the underlying handler types, see [interpolated string token](../../language-reference/tokens/interpolated.md). For performance-focused topics such as `Span<char>` interpolation and custom interpolated string handlers, see [String operations](../../language-reference/builtin-types/string-operations.md).

## Apply a format string

To control how an expression result is formatted, follow the expression with a colon and a [standard or custom format string](../../../standard/base-types/formatting-types.md):

```csharp
{<expression>:<formatString>}
```

The following example formats a date and a numeric value:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="FormatString":::

## Set the field width and alignment

To produce aligned output, follow the expression with a comma and a minimum field width. Positive widths right-align the value, and negative widths left-align it:

```csharp
{<expression>,<width>}
```

:::code language="csharp" source="snippets/interpolation/Program.cs" id="alignment":::

When you need both alignment and a format string, put alignment first:

```csharp
{<expression>,<width>:<formatString>}
```

:::code language="csharp" source="snippets/interpolation/Program.cs" id="AlignmentAndFormat":::

If the formatted value is longer than the requested width, C# ignores the width and emits the full value.

## Escape braces and use escape sequences

Interpolated strings support the same escape sequences as ordinary string literals. To include a literal `{` or `}` character in the result, double it (`{{` or `}}`).

For paths and other strings that contain backslashes, prefer an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) (`$"""..."""`) over the older verbatim form (`$@"..."`). Raw string literals don't process escape sequences, so backslashes appear as-is:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="escapes":::

## Use a conditional expression

The colon has special meaning inside an interpolation expression, so wrap a [conditional operator](../../language-reference/operators/conditional-operator.md) in parentheses:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="conditional":::

## Span an expression across multiple lines

For better readability, break long interpolation expressions across multiple lines. The following example uses an [interpolated raw string literal](../../language-reference/tokens/raw-string.md) so the expression body can wrap freely:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="newlines":::

> [!NOTE]
> Multi-line interpolation expressions are available since C# 11.

## Build constant strings

When every interpolated expression is itself a constant `string`, the whole interpolated string is also a `const`. That makes it usable for attribute arguments, `switch` patterns, and other contexts that require compile-time constants:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="ConstantInterpolated":::

## Format with a specific culture

By default, an interpolated string formats values using <xref:System.Globalization.CultureInfo.CurrentCulture?displayProperty=nameWithType>, which affects date, number, and currency representations. For deterministic output, pass an explicit culture to <xref:System.String.Create(System.IFormatProvider,System.Runtime.CompilerServices.DefaultInterpolatedStringHandler%40)?displayProperty=nameWithType>:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="culture":::

For invariant output (logs, file formats, machine-readable data), pass <xref:System.Globalization.CultureInfo.InvariantCulture?displayProperty=nameWithType>:

:::code language="csharp" source="snippets/interpolation/Program.cs" id="invariant":::

## See also

- [Interpolated string token](../../language-reference/tokens/interpolated.md)
- [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md)
- [Composite formatting](../../../standard/base-types/composite-formatting.md)
- [Formatting types in .NET](../../../standard/base-types/formatting-types.md)
- <xref:System.String.Format*?displayProperty=nameWithType>
- <xref:System.FormattableString?displayProperty=nameWithType>
61 changes: 61 additions & 0 deletions docs/csharp/fundamentals/strings/search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: "Search strings in C#"
description: Learn how to find text within strings in C# with the Contains, StartsWith, EndsWith, IndexOf, and LastIndexOf methods, and how to choose the right StringComparison.
ms.date: 05/21/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# Search strings in C\#

> [!TIP]
> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first.
>
> **Coming from another language?** C# `string` methods such as `Contains`, `StartsWith`, and `IndexOf` parallel methods in Java's `String` and JavaScript's `String.prototype`. The key difference is that some C# searches default to **ordinal, case-sensitive** comparison. Others default to the current culture's semantics. For user-facing searches, you might want to pass a <xref:System.StringComparison> value.

Comment thread
BillWagner marked this conversation as resolved.
The <xref:System.String> class includes methods that answer two everyday questions:

- *Does this string contain that text?* — use <xref:System.String.Contains*>, <xref:System.String.StartsWith*>, or <xref:System.String.EndsWith*>.
- *Where does that text occur?* — use <xref:System.String.IndexOf*> or <xref:System.String.LastIndexOf*>.

For regular expressions, span-based search over `ReadOnlySpan<char>`, and performance considerations, see [String operations](../../language-reference/builtin-types/string-operations.md). For an in-depth treatment of culture-aware comparison, see [Best practices for comparing strings](../../../standard/base-types/best-practices-strings.md).

## Check whether a string contains text

Use `Contains`, `StartsWith`, or `EndsWith` to test for the presence of a substring:

:::code language="csharp" source="snippets/search/Program.cs" id="contains":::

These methods default to **case-sensitive, ordinal** comparison. To accept user input or to ignore case for display text, pass a <xref:System.StringComparison> value such as <xref:System.StringComparison.CurrentCultureIgnoreCase?displayProperty=nameWithType> or <xref:System.StringComparison.OrdinalIgnoreCase?displayProperty=nameWithType>.

When you search for a single character, use the `char` overload of `Contains`. It avoids allocating a one-character string and is more direct:

:::code language="csharp" source="snippets/search/Program.cs" id="ContainsChar":::

## Locate the position of text

`IndexOf` returns the zero-based index of the first occurrence of a substring (or character), and `LastIndexOf` returns the index of the last occurrence. Both return `-1` when the search text isn't present. Combine them to extract the text between two markers:

:::code language="csharp" source="snippets/search/Program.cs" id="IndexOf":::

When you need every occurrence rather than the first or last, iterate by passing the previous result plus one as the `startIndex` argument, or switch to a regular expression.

## Choose the right comparison

Most search overloads accept an optional <xref:System.StringComparison> value. Pick it based on the kind of data you're searching:

- If you're searching identifiers, file paths, protocol tokens, or anything else machine-defined, use <xref:System.StringComparison.Ordinal>.
- If you're searching the same kind of machine-defined data but want case insensitivity, use <xref:System.StringComparison.OrdinalIgnoreCase>.
- If you're searching user-visible text where the current locale's rules should apply, use <xref:System.StringComparison.CurrentCulture>.
- If you're searching that same user-visible text and want to ignore case, use <xref:System.StringComparison.CurrentCultureIgnoreCase>.
- If you're searching persisted data that must compare the same on every machine and culture, use <xref:System.StringComparison.InvariantCulture> (rarely needed).

Ordinal comparison is the fastest option and the right default for anything that isn't natural-language text. Culture-aware comparison is significantly slower and can produce surprising results. For example, in some cultures the lowercase `i` doesn't match an uppercase `I`.Reserve it for searches that users perform against prose.

## See also

- [String operations: pattern matching, performance, and span-based search](../../language-reference/builtin-types/string-operations.md)
- [Best practices for comparing strings in .NET](../../../standard/base-types/best-practices-strings.md)
- [Comparing strings](../../../standard/base-types/comparing.md)
- <xref:System.String?displayProperty=nameWithType>
- <xref:System.StringComparison?displayProperty=nameWithType>
182 changes: 182 additions & 0 deletions docs/csharp/fundamentals/strings/snippets/interpolation/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using System.Globalization;

namespace Interpolation;

public static class Program
{
public static void Main()
{
General();
Console.WriteLine();
FormatString();
Console.WriteLine();
Alignment();
Console.WriteLine();
AlignmentAndFormat();
Console.WriteLine();
Escapes();
Console.WriteLine();
Conditional();
Console.WriteLine();
Newlines();
Console.WriteLine();
ConstantInterpolated();
Console.WriteLine();
Culture();
Console.WriteLine();
Invariant();
}

private static void General()
{
// <general>
double a = 3;
double b = 4;
Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5 * a * b}");
Console.WriteLine($"Length of the hypotenuse of the right triangle with legs of {a} and {b} is {CalculateHypotenuse(a, b)}");
double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2);
// => Area of the right triangle with legs of 3 and 4 is 6
// => Length of the hypotenuse of the right triangle with legs of 3 and 4 is 5
// </general>
}

private static void FormatString()
{
// <FormatString>
var date = new DateTime(1731, 11, 25);
Console.WriteLine($"On {date:dddd, MMMM dd, yyyy} L. Euler introduced the letter e to denote {Math.E:F5}.");
// => On Sunday, November 25, 1731 L. Euler introduced the letter e to denote 2.71828.
// </FormatString>
}

private static void Alignment()
{
// <alignment>
var titles = new Dictionary<string, string>()
{
["Doyle, Arthur Conan"] = "Hound of the Baskervilles, The",
["London, Jack"] = "Call of the Wild, The",
["Shakespeare, William"] = "Tempest, The"
};

Console.WriteLine("Author and Title List");
Console.WriteLine();
Console.WriteLine($"|{"Author",-25}|{"Title",30}|");
foreach (var title in titles)
{
Console.WriteLine($"|{title.Key,-25}|{title.Value,30}|");
}
// => Author and Title List
// =>
// => |Author | Title|
// => |Doyle, Arthur Conan |Hound of the Baskervilles, The|
// => |London, Jack | Call of the Wild, The|
// => |Shakespeare, William | Tempest, The|
// </alignment>
}

private static void AlignmentAndFormat()
{
// <AlignmentAndFormat>
const int NameAlignment = -9;
const int ValueAlignment = 7;
double a = 3;
double b = 4;
Console.WriteLine($"Three classical Pythagorean means of {a} and {b}:");
Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|");
// => Three classical Pythagorean means of 3 and 4:
// => |Arithmetic| 3.500|
// => |Geometric| 3.464|
// => |Harmonic | 3.429|
// </AlignmentAndFormat>
}

private static void Escapes()
{
// <escapes>
int[] xs = [1, 2, 7, 9];
int[] ys = [7, 9, 12];
Console.WriteLine($"Find the intersection of the {{{string.Join(", ", xs)}}} and {{{string.Join(", ", ys)}}} sets.");
// => Find the intersection of the {1, 2, 7, 9} and {7, 9, 12} sets.

var userName = "Jane";
var stringWithEscapes = $"C:\\Users\\{userName}\\Documents";
var rawInterpolated = $"""C:\Users\{userName}\Documents""";
Console.WriteLine(stringWithEscapes);
Console.WriteLine(rawInterpolated);
// => C:\Users\Jane\Documents
// => C:\Users\Jane\Documents
// </escapes>
}

private static void Conditional()
{
// <conditional>
var rand = new Random(42);
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"Coin flip: {(rand.NextDouble() < 0.5 ? "heads" : "tails")}");
}
// </conditional>
}

private static void Newlines()
{
// <newlines>
int[] numbers = [3, 1, 4, 1, 5, 9, 2, 6];
Console.WriteLine($"""
Total: {
numbers.Sum()
}, average: {numbers.Average():F2}.
""");
Comment thread
BillWagner marked this conversation as resolved.
// => Total: 31, average: 3.88.
// </newlines>
}

private static void ConstantInterpolated()
{
// <ConstantInterpolated>
const string Audience = "world";
const string Greeting = $"Hello, {Audience}!";
Console.WriteLine(Greeting);
// => Hello, world!
// </ConstantInterpolated>
}

private static void Culture()
{
// <culture>
CultureInfo[] cultures =
[
CultureInfo.GetCultureInfo("en-US"),
CultureInfo.GetCultureInfo("en-GB"),
CultureInfo.GetCultureInfo("nl-NL"),
CultureInfo.InvariantCulture
];
var date = new DateTime(2026, 5, 21, 12, 35, 31);
var number = 31_415_926.536;
foreach (var culture in cultures)
{
var cultureSpecificMessage = string.Create(culture, $"{date,23}{number,20:N3}");
Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
}
// Output is similar to:
// => en-US 5/21/2026 12:35:31 PM 31,415,926.536
// => en-GB 21/05/2026 12:35:31 31,415,926.536
// => nl-NL 21-05-2026 12:35:31 31.415.926,536
// => 05/21/2026 12:35:31 31,415,926.536
// </culture>
}

private static void Invariant()
{
// <invariant>
var timestamp = new DateTime(2026, 5, 21, 15, 46, 24);
string message = string.Create(CultureInfo.InvariantCulture, $"Date and time in invariant culture: {timestamp}");
Console.WriteLine(message);
// => Date and time in invariant culture: 05/21/2026 15:46:24
// </invariant>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Loading
Loading