-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Integrate .NET5+ string notes into the best practices article and other places #51829
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -34,6 +34,7 @@ When you develop with .NET, follow these recommendations when you compare string | |||||
| - Use overloads that explicitly specify the string comparison rules for string operations. Typically, this involves calling a method overload that has a parameter of type <xref:System.StringComparison>. | ||||||
| - Use <xref:System.StringComparison.Ordinal?displayProperty=nameWithType> or <xref:System.StringComparison.OrdinalIgnoreCase?displayProperty=nameWithType> for comparisons as your safe default for culture-agnostic string matching. | ||||||
| - Use comparisons with <xref:System.StringComparison.Ordinal?displayProperty=nameWithType> or <xref:System.StringComparison.OrdinalIgnoreCase?displayProperty=nameWithType> for better performance. | ||||||
| - Enable [code analyzers](../../fundamentals/code-analysis/overview.md) such as [CA1307](../../fundamentals/code-analysis/quality-rules/ca1307.md), [CA1309](../../fundamentals/code-analysis/quality-rules/ca1309.md), and [CA1310](../../fundamentals/code-analysis/quality-rules/ca1310.md) to detect potentially incorrect string comparisons in your code. | ||||||
| - Use string operations that are based on <xref:System.StringComparison.CurrentCulture?displayProperty=nameWithType> when you display output to the user. | ||||||
| - Use the non-linguistic <xref:System.StringComparison.Ordinal?displayProperty=nameWithType> or <xref:System.StringComparison.OrdinalIgnoreCase?displayProperty=nameWithType> values instead of string operations based on <xref:System.Globalization.CultureInfo.InvariantCulture%2A?displayProperty=nameWithType> when the comparison is linguistically irrelevant (symbolic, for example). | ||||||
| - Use the <xref:System.String.ToUpperInvariant%2A?displayProperty=nameWithType> method instead of the <xref:System.String.ToLowerInvariant%2A?displayProperty=nameWithType> method when you normalize strings for comparison. | ||||||
|
|
@@ -90,6 +91,28 @@ However, evaluating two strings for equality or sort order doesn't yield a singl | |||||
|
|
||||||
| In addition, string comparisons using different versions of .NET or using .NET on different operating systems or operating system versions may return different results. For more information, see [Strings and the Unicode Standard](xref:System.String#Unicode). | ||||||
|
|
||||||
| ### Globalization libraries: .NET vs .NET Framework | ||||||
|
|
||||||
| .NET and .NET Framework use different globalization libraries, which can affect string comparison behavior: | ||||||
|
|
||||||
| - **.NET** uses the [International Components for Unicode (ICU)](https://icu.unicode.org/) libraries for globalization functionality across all platforms (Windows, Linux, macOS). ICU is an industry-standard Unicode implementation that provides consistent behavior across operating systems. | ||||||
| - **.NET Framework** uses [National Language Support (NLS)](/windows/win32/intl/national-language-support) APIs on Windows, which is a Windows-specific globalization system. | ||||||
|
|
||||||
| Because ICU and NLS implement different logic in their linguistic comparers, the same string comparison code can produce different results depending on which runtime you're using. Consider the following example that formats a number as currency using a German culture: | ||||||
|
|
||||||
| :::code language="csharp" source="./snippets/best-practices-strings/csharp/icu-demo/Program.cs"::: | ||||||
| :::code language="vb" source="./snippets/best-practices-strings/vb/icu-demo/Program.vb"::: | ||||||
|
|
||||||
| When running on .NET Framework, the output is `"100,00 €"` (using the euro symbol). On .NET, the output is `"100,00 ¤"` (using the international currency symbol). This difference occurs because ICU treats currency as a property of a country or region, not just a language, whereas the language-only German culture (`"de"`) doesn't specify a country. | ||||||
|
|
||||||
|
Comment on lines
+106
to
+107
|
||||||
| If your application requires the older NLS behavior when running on .NET, you can enable it through [runtime configuration](../../core/runtime-config/globalization.md#nls). However, for new applications, we recommend using explicit `StringComparison` parameters to make string comparison behavior clear and consistent. | ||||||
|
||||||
| If your application requires the older NLS behavior when running on .NET, you can enable it through [runtime configuration](../../core/runtime-config/globalization.md#nls). However, for new applications, we recommend using explicit `StringComparison` parameters to make string comparison behavior clear and consistent. | |
| If your application requires the older NLS behavior when running on .NET, you can enable it through [runtime configuration](../../core/runtime-config/globalization.md#nls). For new applications, use explicit `StringComparison` parameters to make string comparison behavior clear and consistent. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using System; | ||
| using System.Globalization; | ||
|
|
||
| // Demonstrate Unicode normalization with résumé | ||
| Console.WriteLine("=== Unicode Normalization Example ==="); | ||
| Console.WriteLine("resume".IndexOf("e", StringComparison.Ordinal)); // prints '1' | ||
| Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e", StringComparison.Ordinal)); // prints '-1' | ||
| Console.WriteLine("r\u00E9sume\u0301".IndexOf("e", StringComparison.Ordinal)); // prints '5' | ||
| Console.WriteLine("re\u0301sum\u00E9".IndexOf("e", StringComparison.Ordinal)); // prints '1' | ||
| Console.WriteLine("re\u0301sume\u0301".IndexOf("e", StringComparison.Ordinal)); // prints '1' | ||
|
|
||
| // Linguistic comparison | ||
| Console.WriteLine("\n=== Linguistic Comparison Example ==="); | ||
| Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e")); // prints '-1' (not found) | ||
| Console.WriteLine("r\u00E9sum\u00E9".IndexOf("\u00E9")); // prints '1' | ||
| Console.WriteLine("\u00E9".IndexOf("e\u0301")); // prints '0' | ||
|
|
||
| // Hungarian culture-aware comparison | ||
| Console.WriteLine("\n=== Hungarian Culture-Aware Example ==="); | ||
| CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU"); | ||
| Console.WriteLine("endz".EndsWith("z")); // Prints 'False' | ||
|
|
||
| CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; | ||
| Console.WriteLine("endz".EndsWith("z")); // Prints 'True' |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,9 @@ | ||||||||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||||||||
|
|
||||||||||
| <PropertyGroup> | ||||||||||
| <OutputType>Exe</OutputType> | ||||||||||
| <TargetFramework>net9.0</TargetFramework> | ||||||||||
|
||||||||||
| <TargetFramework>net9.0</TargetFramework> | |
| <TargetFramework>net8.0</TargetFramework> | |
| <Nullable>enable</Nullable> | |
| <ImplicitUsings>enable</ImplicitUsings> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or do you want to upgrade to .NET 10?
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using System; | ||
| using System.Globalization; | ||
|
|
||
| System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("de"); | ||
| string text = string.Format("{0:C}", 100); | ||
| Console.WriteLine($"Currency formatted: {text}"); | ||
|
|
||
| // Output on .NET Framework (NLS): "100,00 €" | ||
| // Output on .NET (ICU): "100,00 ¤" |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,9 @@ | ||||||||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||||||||
|
|
||||||||||
| <PropertyGroup> | ||||||||||
| <OutputType>Exe</OutputType> | ||||||||||
| <TargetFramework>net9.0</TargetFramework> | ||||||||||
|
||||||||||
| <TargetFramework>net9.0</TargetFramework> | |
| <TargetFramework>net8.0</TargetFramework> | |
| <Nullable>enable</Nullable> | |
| <ImplicitUsings>enable</ImplicitUsings> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section states that .NET uses ICU “across all platforms (Windows, Linux, macOS)”. On Windows, .NET’s default globalization library depends on the OS version and runtime version (for example, ICU on Windows 10 19H1+ starting in .NET 5, ICU on Windows Server 2019 starting in .NET 7). Consider qualifying this statement (and/or linking to the ICU-on-Windows details) to avoid implying ICU is always the default on Windows.