Skip to content
Merged
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
35 changes: 27 additions & 8 deletions .openpublishing.redirection.csharp.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
{
"redirections": [
{
"source_path_from_root": "/docs/csharp/how-to/compare-strings.md",
"redirect_url": "/dotnet/csharp/fundamentals/strings/common-tasks/compare",
"redirect_document_id": true
},
{
"source_path_from_root": "/docs/csharp/how-to/concatenate-multiple-strings.md",
"redirect_url": "/dotnet/csharp/fundamentals/strings/common-tasks/concatenate",
"redirect_document_id": true
},
{
"source_path_from_root": "/docs/csharp/how-to/modify-string-contents.md",
"redirect_url": "/dotnet/csharp/fundamentals/strings/common-tasks/modify",
"redirect_document_id": true
},
{
"source_path_from_root": "/docs/csharp/tutorials/string-interpolation.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/string-interpolation",
"redirect_document_id": true
},
{
"source_path_from_root": "/docs/csharp/fundamentals/null-safety/migration-strategies.md",
"redirect_url": "/dotnet/csharp/advanced-topics/update-applications/nullable-migration-strategies"
Expand Down Expand Up @@ -5095,11 +5115,11 @@
},
{
"source_path_from_root": "/docs/csharp/programming-guide/strings/how-to-compare-strings.md",
"redirect_url": "/dotnet/csharp/how-to/compare-strings"
"redirect_url": "/dotnet/csharp/fundamentals/strings/common-tasks/compare"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/strings/how-to-concatenate-multiple-strings.md",
"redirect_url": "/dotnet/csharp/how-to/concatenate-multiple-strings"
"redirect_url": "/dotnet/csharp/fundamentals/strings/common-tasks/concatenate"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/strings/how-to-convert-a-string-to-a-datetime.md",
Expand All @@ -5115,7 +5135,7 @@
},
{
"source_path_from_root": "/docs/csharp/programming-guide/strings/how-to-modify-string-contents.md",
"redirect_url": "/dotnet/csharp/how-to/modify-string-contents"
"redirect_url": "/dotnet/csharp/fundamentals/strings/common-tasks/modify"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/strings/how-to-parse-strings-using-string-split.md",
Expand Down Expand Up @@ -5338,12 +5358,11 @@
},
{
"source_path_from_root": "/docs/csharp/quick-starts/interpolated-strings-local.md",
"redirect_url": "/dotnet/csharp/tutorials/string-interpolation"
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/string-interpolation"
},
{
"source_path_from_root": "/docs/csharp/quick-starts/interpolated-strings.yml",
"redirect_url": "/dotnet/csharp/tutorials/string-interpolation",
"redirect_document_id": true
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/string-interpolation"
},
{
"source_path_from_root": "/docs/csharp/quick-starts/introduction-to-classes.md",
Expand Down Expand Up @@ -5482,11 +5501,11 @@
},
{
"source_path_from_root": "/docs/csharp/tutorials/exploration/interpolated-strings-local.md",
"redirect_url": "/dotnet/csharp/tutorials/string-interpolation"
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/string-interpolation"
},
{
"source_path_from_root": "/docs/csharp/tutorials/exploration/interpolated-strings.yml",
"redirect_url": "/dotnet/csharp/tutorials/string-interpolation"
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/string-interpolation"
},
{
"source_path_from_root": "/docs/csharp/tutorials/exploration/patterns-objects.md",
Expand Down
10 changes: 5 additions & 5 deletions docs/core/diagnostics/metrics-instrumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(name: "hatco.store.hats_sold",
unit: "{hats}",
unit: "{hat}",
description: "The number of hats sold in our store");

static void Main(string[] args)
Expand All @@ -390,18 +390,18 @@ Press p to pause, r to resume, q to quit.

Name Current Value
[HatCo.Store]
hatco.store.hats_sold ({hats}) 40
hatco.store.hats_sold ({hat}) 40
```

dotnet-counters doesn't currently use the description text in the UI, but it does show the unit when it is provided. In this case, you see "{hats}"
dotnet-counters doesn't currently use the description text in the UI, but it does show the unit when it is provided. In this case, you see "{hat}"
has replaced the generic term "Count" that is visible in previous descriptions.

### Best practices

- .NET APIs allow any string to be used as the unit, but we recommend using [UCUM](https://ucum.org/), an international standard for unit names. The curly
braces around "{hats}" is part of the UCUM standard, indicating that it is a descriptive annotation rather than a unit name with a standardized meaning like seconds or bytes.
braces around "{hat}" is part of the UCUM standard, indicating that it is a descriptive annotation rather than a unit name with a standardized meaning like seconds or bytes. For more information, see [Metrics semantic conventions - Instrument units](https://opentelemetry.io/docs/specs/semconv/general/metrics/#instrument-units).

- The unit specified in the constructor should describe the units appropriate for an individual measurement. This will sometimes differ from the units on the final reported metric. In this example, each measurement is a number of hats, so "{hats}" is the appropriate unit to pass in the constructor. The collection tool could have calculated the rate of change and derived on its own that the appropriate unit for the calculated rate metric is {hats}/sec.
- The unit specified in the constructor should describe the units appropriate for an individual measurement. This will sometimes differ from the units on the final reported metric. In this example, each measurement is a number of hats, so "{hat}" is the appropriate unit to pass in the constructor. The collection tool could have calculated the rate of change and derived on its own that the appropriate unit for the calculated rate metric is {hat}/sec.

- When recording measurements of time, prefer units of seconds recorded as a floating point or double value.

Expand Down
2 changes: 1 addition & 1 deletion docs/core/extensions/logging/library-guidance.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ For more information, see [Compile-time logging source generation](source-genera

## Avoid string interpolation in logging

A common mistake is to use [string interpolation](../../../csharp/tutorials/string-interpolation.md) to build log messages. String interpolation in logging is problematic for performance, as the string is evaluated even if the corresponding `LogLevel` isn't enabled. Instead of string interpolation, use the log message template, formatting, and argument list. For more information, see [Logging in .NET: Log message template](overview.md#log-message-template).
A common mistake is to use [string interpolation](../../../csharp/fundamentals/tutorials/string-interpolation.md) to build log messages. String interpolation in logging is problematic for performance, as the string is evaluated even if the corresponding `LogLevel` isn't enabled. Instead of string interpolation, use the log message template, formatting, and argument list. For more information, see [Logging in .NET: Log message template](overview.md#log-message-template).

## Use no-op logging defaults

Expand Down
1 change: 1 addition & 0 deletions docs/core/testing/microsoft-testing-platform-telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The telemetry feature collects the following data points:
| All | Version of your test adapter. |
| All | Guid to correlate events from a single runner. |
| 1.0.3 | Guid to correlate events from a single test run. |
| 2.1.0 | Count of tests discovered. |

## Continuous integration detection

Expand Down
70 changes: 70 additions & 0 deletions docs/csharp/fundamentals/strings/common-tasks/compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: "Compare strings in C#"
description: Learn how to compare strings in C# for equality and sort order, how ordinal and culture-aware comparisons differ, and how to choose a StringComparison value.
ms.date: 06/24/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# Compare 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# `==` on strings compares *values*, not references, much like Java's `equals` or JavaScript's `===`. The key difference is that C# lets you choose between **ordinal** (binary) and **culture-aware** comparison through a <xref:System.StringComparison> value.

You compare strings to answer one of two questions: *Are these two strings equal?* or *In what order do these strings sort?* C# lets you control two independent factors when you answer either question:

- **Case sensitivity** — whether `"Hello"` and `"hello"` are treated as equal.
- **Comparison kind** — *ordinal* (compare the binary value of each character) or *culture-aware* (apply the linguistic rules of a culture).

The <xref:System.StringComparison> enumeration combines these factors into a single value you pass to comparison methods.

## Compare for equality

The <xref:System.String.Equals*?displayProperty=nameWithType> method and the `==` operator both perform a **case-sensitive, ordinal** comparison by default. Ordinal comparison checks the binary value of each character, so it's fast and gives the same result on every machine. You can make your intent explicit by calling the overload that takes a <xref:System.StringComparison> value:

:::code language="csharp" source="snippets/compare/Program.cs" id="DefaultEquality":::

To ignore case while keeping ordinal semantics, pass <xref:System.StringComparison.OrdinalIgnoreCase?displayProperty=nameWithType>:

:::code language="csharp" source="snippets/compare/Program.cs" id="IgnoreCase":::

## Compare for sort order

To determine sort order rather than equality, use <xref:System.String.Compare*?displayProperty=nameWithType>. It returns a negative number, zero, or a positive number to indicate whether the first string sorts before, at the same position as, or after the second:

:::code language="csharp" source="snippets/compare/Program.cs" id="Order":::

> [!IMPORTANT]
> `Compare` and `CompareTo` default to a *culture-aware* comparison, while `Equals` and `==` default to *ordinal*. To avoid surprises, pass an explicit <xref:System.StringComparison> value so your code states which behavior it wants.

## Compare against constants with pattern matching `is` or `switch`

When the value you compare against is a constant, you can use the [`is` operator](../../../language-reference/operators/is.md) with a [constant pattern](../../../language-reference/operators/patterns.md#constant-pattern) as a readable alternative to `==`:

:::code language="csharp" source="snippets/compare/Program.cs" id="ConstantPattern":::

To compare a string against several constants, use a [`switch` expression](../../../language-reference/operators/switch-expression.md). Each arm tests a [constant pattern](../../../language-reference/operators/patterns.md#constant-pattern), and the discard pattern (`_`) handles every value that doesn't match. The next example maps a direction keyword to a travel instruction:

:::code language="csharp" source="snippets/compare/Program.cs" id="SwitchExpression":::

A `switch` expression that tests string constants performs the same case-sensitive, ordinal comparison as `==`, so `"north"` doesn't match the `"North"` arm.

## Choose the right comparison

Pick the comparison kind based on the data, not out of habit:

- For identifiers, file paths, protocol tokens, and other machine-defined text, use <xref:System.StringComparison.Ordinal> (or <xref:System.StringComparison.OrdinalIgnoreCase> to ignore case). Ordinal comparison is fast and consistent across cultures.
- For text that users read and sort, such as names or product titles, use <xref:System.StringComparison.CurrentCulture> so the order matches the user's expectations.

Culture-aware comparison applies linguistic rules that vary by culture and can produce surprising results. For example, some cultures treat `"ss"` and `"ß"` as equal, and the order of strings can change between machines. Because of that variability, reserve culture-aware comparison for genuine natural-language text, and use the same comparison kind whenever you both sort and search a collection.

For an in-depth treatment of culture-sensitive comparison, including globalization considerations and platform differences, see [Best practices for comparing strings in .NET](../../../../standard/base-types/best-practices-strings.md).

## See also

- [Best practices for comparing strings in .NET](../../../../standard/base-types/best-practices-strings.md)
- [Search strings in C#](search.md)
- <xref:System.StringComparison>
- <xref:System.String.Compare*?displayProperty=nameWithType>
62 changes: 62 additions & 0 deletions docs/csharp/fundamentals/strings/common-tasks/concatenate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: "Concatenate strings in C#"
description: Learn how to combine strings in C# with the + operator, string interpolation, string.Concat, string.Join, and StringBuilder, and how to choose between them.
ms.date: 06/24/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# Concatenate 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# concatenation with `+` parallels Java and JavaScript. C# adds [string interpolation](../interpolation.md) (`$"{x}"`), similar to JavaScript template literals and Python f-strings, as the preferred way to build strings from variables. For building strings in a loop, C# offers <xref:System.Text.StringBuilder>, much like Java's `StringBuilder`.

*Concatenation* appends one string to the end of another to produce a new string. C# gives you several ways to concatenate, and the best choice depends on whether you're joining a fixed set of values or a collection, or building a string piece by piece in a loop.

## Concatenate string literals

When you concatenate string literals or constants with `+`, the compiler joins them at compile time. Splitting a long literal across several lines improves readability in source without any run-time cost:

:::code language="csharp" source="snippets/concatenate/Program.cs" id="Literals":::

## Use the `+` and `+=` operators

To combine string variables, use the `+` operator to produce a new string, or `+=` to append to an existing one. The `+` operator is intuitive, and the compiler copies the string content only once even when you chain several operators in a single expression:

:::code language="csharp" source="snippets/concatenate/Program.cs" id="Operators":::

> [!NOTE]
> In string concatenation, C# treats a `null` string the same as an empty string, so concatenating `null` adds nothing to the result.

## Use string interpolation

To embed computed expressions in a string, prefer [string interpolation](../interpolation.md) over positional placeholders like the `{0}` and `{1}` tokens that <xref:System.String.Format*?displayProperty=nameWithType> inherits from C-style formatting. Interpolation places each expression inline where its value appears, so the result string stays readable and you can't misalign an argument:

:::code language="csharp" source="snippets/concatenate/Program.cs" id="Interpolation":::

When every interpolated expression is itself a constant string, you can assign the interpolated result to a `const` string.

## Join a collection of strings

To combine the elements of a collection, use <xref:System.String.Concat*?displayProperty=nameWithType> to join them with no separator, or <xref:System.String.Join*?displayProperty=nameWithType> to place a separator between each element:

:::code language="csharp" source="snippets/concatenate/Program.cs" id="ConcatJoin":::

`string.Join` is the right tool whenever you need delimited output, such as comma-separated values or space-separated words.

## Build a string in a loop

Each `+` or `+=` operation creates a new string, because strings are immutable. When you append many pieces in a loop, that allocation adds up. The <xref:System.Text.StringBuilder> class builds the result in a single buffer instead:

:::code language="csharp" source="snippets/concatenate/Program.cs" id="StringBuilder":::

Reach for `StringBuilder` when the number of pieces is large or unknown at compile time. For a fixed, small set of values, the `+` operator and string interpolation are clearer. For guidance on when each approach performs best, see [The `string` and `StringBuilder` types](xref:System.Text.StringBuilder#the-string-and-stringbuilder-types).

## See also

- [String interpolation in C#](../interpolation.md)
- <xref:System.String.Concat*?displayProperty=nameWithType>
- <xref:System.String.Join*?displayProperty=nameWithType>
- <xref:System.Text.StringBuilder>
Loading
Loading