Skip to content
3 changes: 2 additions & 1 deletion docs/core/whats-new/dotnet-8/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,8 @@ IDataView predictions = model.Transform(split.TestSet);
}
```

- Methods like <xref:System.MemoryExtensions.IndexOfAny%2A?displayProperty=nameWithType> look for the first occurrence of *any value in the passed collection*. The new <xref:System.Buffers.SearchValues%601?displayProperty=fullName> type is designed to be passed to such methods. Correspondingly, .NET 8 adds new overloads of methods like <xref:System.MemoryExtensions.IndexOfAny%2A?displayProperty=nameWithType> that accept an instance of the new type. When you create an instance of <xref:System.Buffers.SearchValues%601>, all the data that's necessary to optimize subsequent searches is derived *at that time*, meaning the work is done up front.
- Methods like <xref:System.MemoryExtensions.IndexOfAny%2A?displayProperty=nameWithType> look for the first occurrence of *any value in the passed collection*. The new <xref:System.Buffers.SearchValues%601?displayProperty=fullName> type is designed to be passed to such methods. Correspondingly, .NET 8 adds new overloads of methods like <xref:System.MemoryExtensions.IndexOfAny%2A?displayProperty=nameWithType> that accept an instance of the new type. When you create an instance of <xref:System.Buffers.SearchValues%601>, all the data that's necessary to optimize subsequent searches is derived *at that time*, meaning the work is done up front. (The `SearchValues<T>` type was expanded in .NET 9. For more information, see [`SearchValues` expansion](../dotnet-9/libraries.md#searchvalues-expansion).)

- The new <xref:System.Text.CompositeFormat?displayProperty=fullName> type is useful for optimizing format strings that aren't known at compile time (for example, if the format string is loaded from a resource file). A little extra time is spent up front to do work such as parsing the string, but it saves the work from being done on each use.

```csharp
Expand Down
16 changes: 8 additions & 8 deletions docs/csharp/how-to/search-strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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:
helpviewer_keywords:
- "searching strings [C#]"
- "strings [C#], searching with String methods"
- "strings [C#], searching with regular expressions"
Expand Down Expand Up @@ -51,13 +51,13 @@ The search pattern describes the text you search for. The following table descri

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 |
| 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":::

Expand Down
4 changes: 2 additions & 2 deletions docs/fundamentals/code-analysis/quality-rules/ca1307.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ms.author: gewarren
| **Title** | Specify StringComparison for clarity |
| **Category** | [Globalization](globalization-warnings.md) |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default in .NET 10** | No |
| **Enabled by default in .NET 10** | No |

## Cause

Expand All @@ -31,7 +31,7 @@ A string comparison operation uses a method overload that does not set a <xref:S

Many string compare operations provide an overload that accepts a <xref:System.StringComparison> enumeration value as a parameter.

Whenever an overload exists that takes a <xref:System.StringComparison> parameter, it should be used instead of an overload that does not take this parameter. By explicitly setting this parameter, your code is often made clearer and easier to maintain. For more information, see [Specifying string comparisons explicitly](../../../standard/base-types/best-practices-strings.md#specifying-string-comparisons-explicitly).
Whenever an overload exists that takes a <xref:System.StringComparison> parameter, it should be used instead of an overload that does not take this parameter. By explicitly setting this parameter, your code is often made clearer and easier to maintain. For more information, see [Specify string comparisons explicitly](../../../standard/base-types/best-practices-strings.md#specify-string-comparisons-explicitly).

> [!NOTE]
> This rule does not consider the default <xref:System.StringComparison> value used by the comparison method. Hence, it can be potentially noisy for methods that use the `Ordinal` string comparison by default and the user intended to use this default compare mode.
Expand Down
6 changes: 5 additions & 1 deletion docs/fundamentals/code-analysis/quality-rules/ca1870.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ms.author: mizupan
| **Title** | Use a cached 'SearchValues' instance |
| **Category** | [Performance](performance-warnings.md) |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default in .NET 10** | As suggestion |
| **Enabled by default in .NET 10** | As suggestion |

## Cause

Expand Down Expand Up @@ -97,3 +97,7 @@ dotnet_diagnostic.CA1870.severity = none
```

For more information, see [How to suppress code analysis warnings](../suppress-warnings.md).

## See also

- [`MemoryExtensions.AsSpan.IndexOfAny` and the `SearchValues<T>` type](../../../standard/base-types/best-practices-strings.md#memoryextensionsasspanindexofany-and-the-searchvaluest-type)
64 changes: 42 additions & 22 deletions docs/standard/base-types/best-practices-strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ helpviewer_keywords:
- "string sorting"
- "comparing strings"
- "strings [.NET],comparing"
ms.assetid: b9f0bf53-e2de-4116-8ce9-d4f91a1df4f7
---
# Best practices for comparing strings in .NET

Expand Down Expand Up @@ -48,18 +47,18 @@ Avoid the following practices when you compare strings:
- Don't use string operations based on <xref:System.StringComparison.InvariantCulture?displayProperty=nameWithType> in most cases. One of the few exceptions is when you're persisting linguistically meaningful but culturally agnostic data.
- Don't use an overload of the <xref:System.String.Compare%2A?displayProperty=nameWithType> or <xref:System.String.CompareTo%2A> method and test for a return value of zero to determine whether two strings are equal.

## Specifying string comparisons explicitly
## Specify string comparisons explicitly

Most of the string manipulation methods in .NET are overloaded. Typically, one or more overloads accept default settings, whereas others accept no defaults and instead define the precise way in which strings are to be compared or manipulated. Most of the methods that don't rely on defaults include a parameter of type <xref:System.StringComparison>, which is an enumeration that explicitly specifies rules for string comparison by culture and case. The following table describes the <xref:System.StringComparison> enumeration members.

|StringComparison member|Description|
|-----------------------------|-----------------|
|<xref:System.StringComparison.CurrentCulture>|Performs a case-sensitive comparison using the current culture.|
|<xref:System.StringComparison.CurrentCultureIgnoreCase>|Performs a case-insensitive comparison using the current culture.|
|<xref:System.StringComparison.InvariantCulture>|Performs a case-sensitive comparison using the invariant culture.|
|<xref:System.StringComparison.InvariantCultureIgnoreCase>|Performs a case-insensitive comparison using the invariant culture.|
|<xref:System.StringComparison.Ordinal>|Performs an ordinal comparison.|
|<xref:System.StringComparison.OrdinalIgnoreCase>|Performs a case-insensitive ordinal comparison.|
| `StringComparison` member | Description |
|---------------------------------------------------------|-------------------------------------------------------------------|
| <xref:System.StringComparison.CurrentCulture> | Performs a case-sensitive comparison using the current culture. |
| <xref:System.StringComparison.CurrentCultureIgnoreCase> | Performs a case-insensitive comparison using the current culture. |
| <xref:System.StringComparison.InvariantCulture> | Performs a case-sensitive comparison using the invariant culture. |
| <xref:System.StringComparison.InvariantCultureIgnoreCase> | Performs a case-insensitive comparison using the invariant culture. |
| <xref:System.StringComparison.Ordinal> | Performs an ordinal comparison. |
| <xref:System.StringComparison.OrdinalIgnoreCase> | Performs a case-insensitive ordinal comparison. |

For example, the <xref:System.String.IndexOf%2A> method, which returns the index of a substring in a <xref:System.String> object that matches either a character or a string, has nine overloads:

Expand Down Expand Up @@ -188,12 +187,12 @@ When interpreting file names, cookies, or anything else where a combination such

On balance, the invariant culture has few properties that make it useful for comparison. It does comparison in a linguistically relevant manner, which prevents it from guaranteeing full symbolic equivalence, but it isn't the choice for display in any culture. One of the few reasons to use <xref:System.StringComparison.InvariantCulture?displayProperty=nameWithType> for comparison is to persist ordered data for a cross-culturally identical display. For example, if a large data file that contains a list of sorted identifiers for display accompanies an application, adding to this list would require an insertion with invariant-style sorting.

## Choosing a StringComparison member for your method call
## How to choose a StringComparison member

The following table outlines the mapping from semantic string context to a <xref:System.StringComparison> enumeration member:

|Data|Behavior|Corresponding System.StringComparison<br /><br /> value|
|----------|--------------|-----------------------------------------------------|
| Data | Behavior | Corresponding System.StringComparison<br /><br /> value |
|------|----------|---------------------------------------------------------|
|Case-sensitive internal identifiers.<br /><br /> Case-sensitive identifiers in standards such as XML and HTTP.<br /><br /> Case-sensitive security-related settings.|A non-linguistic identifier, where bytes match exactly.|<xref:System.StringComparison.Ordinal>|
|Case-insensitive internal identifiers.<br /><br /> Case-insensitive identifiers in standards such as XML and HTTP.<br /><br /> File paths.<br /><br /> Registry keys and values.<br /><br /> Environment variables.<br /><br /> Resource identifiers (for example, handle names).<br /><br /> Case-insensitive security-related settings.|A non-linguistic identifier, where case is irrelevant.|<xref:System.StringComparison.OrdinalIgnoreCase>|
|Some persisted, linguistically relevant data.<br /><br /> Display of linguistic data that requires a fixed sort order.|Culturally agnostic data that still is linguistically relevant.|<xref:System.StringComparison.InvariantCulture><br /><br /> -or-<br /><br /> <xref:System.StringComparison.InvariantCultureIgnoreCase>|
Expand All @@ -203,15 +202,15 @@ The following table outlines the mapping from semantic string context to a <xref

The following sections describe the methods that are most commonly used for string comparison.

### String.Compare
### `String.Compare`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

As the operation most central to string interpretation, all instances of these method calls should be examined to determine whether strings should be interpreted according to the current culture, or dissociated from the culture (symbolically). Typically, it's the latter, and a <xref:System.StringComparison.Ordinal?displayProperty=nameWithType> comparison should be used instead.

The <xref:System.Globalization.CompareInfo?displayProperty=nameWithType> class, which is returned by the <xref:System.Globalization.CultureInfo.CompareInfo%2A?displayProperty=nameWithType> property, also includes a <xref:System.Globalization.CompareInfo.Compare%2A> method that provides a large number of matching options (ordinal, ignoring white space, ignoring kana type, and so on) by means of the <xref:System.Globalization.CompareOptions> flag enumeration.

### String.CompareTo
### `String.CompareTo`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

Expand All @@ -222,13 +221,13 @@ Types that implement the <xref:System.IComparable> and <xref:System.IComparable%
:::code language="csharp" source="./snippets/best-practices-strings/csharp/stringcomparer/Program.cs" id="class":::
:::code language="vb" source="./snippets/best-practices-strings/vb/stringcomparer/Program.vb" id="class":::

### String.Equals
### `String.Equals`

Default interpretation: <xref:System.StringComparison.Ordinal?displayProperty=nameWithType>.

The <xref:System.String> class lets you test for equality by calling either the static or instance <xref:System.String.Equals%2A> method overloads, or by using the static equality operator. The overloads and operator use ordinal comparison by default. However, we still recommend that you call an overload that explicitly specifies the <xref:System.StringComparison> type even if you want to perform an ordinal comparison; this makes it easier to search code for a certain string interpretation.

### String.ToUpper and String.ToLower
### `String.ToUpper` and `String.ToLower`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

Expand All @@ -238,26 +237,47 @@ The <xref:System.String.ToUpperInvariant%2A?displayProperty=nameWithType> and <x

Overloads are also available for converting to uppercase and lowercase in a specific culture, by passing a <xref:System.Globalization.CultureInfo> object that represents that culture to the method.

### Char.ToUpper and Char.ToLower
### `Char.ToUpper` and `Char.ToLower`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

The <xref:System.Char.ToUpper(System.Char)?displayProperty=nameWithType> and <xref:System.Char.ToLower(System.Char)?displayProperty=nameWithType> methods work similarly to the <xref:System.String.ToUpper?displayProperty=nameWithType> and <xref:System.String.ToLower?displayProperty=nameWithType> methods described in the previous section.

### String.StartsWith and String.EndsWith
### `String.StartsWith` and `String.EndsWith`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

By default, both of these methods perform a culture-sensitive comparison. In particular, they may ignore non-printing characters.

### String.IndexOf and String.LastIndexOf
### `String.IndexOf` and `String.LastIndexOf`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

There's a lack of consistency in how the default overloads of these methods perform comparisons. All <xref:System.String.IndexOf%2A?displayProperty=nameWithType> and <xref:System.String.LastIndexOf%2A?displayProperty=nameWithType> methods that include a <xref:System.Char> parameter perform an ordinal comparison, but the default <xref:System.String.IndexOf%2A?displayProperty=nameWithType> and <xref:System.String.LastIndexOf%2A?displayProperty=nameWithType> methods that include a <xref:System.String> parameter perform a culture-sensitive comparison.

If you call the <xref:System.String.IndexOf%28System.String%29?displayProperty=nameWithType> or <xref:System.String.LastIndexOf%28System.String%29?displayProperty=nameWithType> method and pass it a string to locate in the current instance, we recommend that you call an overload that explicitly specifies the <xref:System.StringComparison> type. The overloads that include a <xref:System.Char> argument don't allow you to specify a <xref:System.StringComparison> type.

### `MemoryExtensions.AsSpan.IndexOfAny` and the `SearchValues<T>` type

.NET 8 introduced the <xref:System.Buffers.SearchValues`1> type, which provides an optimized solution for searching for specific sets of characters or bytes within spans.

If you're comparing a string against a fixed set of known values repeatedly, consider using the <xref:System.Buffers.SearchValues`1.Contains(`0)?displayProperty=nameWithType> method instead of chained comparisons or LINQ-based approaches. `SearchValues<T>` can precompute internal lookup structures and optimize the comparison logic based on the provided values. To see performance benefits, create and cache the `SearchValues<string>` instance once, then reuse it for comparisons:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shsat94 I moved your new paragraph to this article instead. The article you put it in was about globalization changes.


```csharp
using System.Buffers;

private static readonly SearchValues<string> Commands = SearchValues.Create(
new[] { "start", "run", "go", "begin", "commence" },
StringComparison.OrdinalIgnoreCase);

if (Commands.Contains(command))
{
// ...
}
```

In .NET 9, `SearchValues` was extended to support searching for substrings within a larger string. For an example, see [`SearchValues` expansion](../../core/whats-new/dotnet-9/libraries.md#searchvalues-expansion).

## Methods that perform string comparison indirectly

Some non-string methods that have string comparison as a central operation use the <xref:System.StringComparer> type. The <xref:System.StringComparer> class includes six static properties that return <xref:System.StringComparer> instances whose <xref:System.StringComparer.Compare%2A?displayProperty=nameWithType> methods perform the following types of string comparisons:
Expand All @@ -269,7 +289,7 @@ Some non-string methods that have string comparison as a central operation use t
- Ordinal comparison. This <xref:System.StringComparer> object is returned by the <xref:System.StringComparer.Ordinal%2A?displayProperty=nameWithType> property.
- Case-insensitive ordinal comparison. This <xref:System.StringComparer> object is returned by the <xref:System.StringComparer.OrdinalIgnoreCase%2A?displayProperty=nameWithType> property.

### Array.Sort and Array.BinarySearch
### `Array.Sort` and `Array.BinarySearch`

Default interpretation: <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType>.

Expand All @@ -288,7 +308,7 @@ If this data is persisted and moved across cultures, and sorting is used to pres
:::code language="csharp" source="./snippets/best-practices-strings/csharp/indirect1/binarysearch.cs" id="invariant" highlight="11,15":::
:::code language="vb" source="./snippets/best-practices-strings/vb/indirect1/binarysearch.vb" id="invariant" highlight="10,14":::

### Collections example: Hashtable constructor
### Collections example: `Hashtable` constructor

Hashing strings provides a second example of an operation that is affected by the way in which strings are compared.

Expand Down
Loading