在 .NET 5 及更高版本中比较字符串时的行为更改Behavior changes when comparing strings on .NET 5+

.NET 5.0 引入了一项运行时行为更改,其中,全球化 API 目前在所有支持的平台上默认使用 ICU.NET 5.0 introduces a runtime behavioral change where globalization APIs now use ICU by default across all supported platforms. 这明显有别于较早的 .NET Core 和 .NET Framework 版本,在 Windows 上运行这些版本时,它们利用操作系统的区域语言支持 (NLS) 功能。This is a departure from earlier versions of .NET Core and from .NET Framework, which utilize the operating system's national language support (NLS) functionality when running on Windows. 有关这些更改的详细信息,包括还原该行为更改的兼容性开关,请参阅 .NET 全球化和 ICUFor more information on these changes, including compatibility switches that can revert the behavior change, see .NET globalization and ICU.

更改原因Reason for change

引入此更改是为了统一所有支持的操作系统上的 .NET 的全球化行为。This change was introduced to unify .NET's globalization behavior across all supported operating systems. 它还能让应用程序捆绑自己的全球化库,而不是依赖于操作系统的内置库。It also provides the ability for applications to bundle their own globalization libraries rather than depend on the OS's built-in libraries. 有关详细信息,请参阅中断性变更For more information, see the breaking change notification.

行为差异Behavioral differences

如果在使用 string.IndexOf(string) 这样的函数时不调用使用 StringComparison 参数的重载,则可能在计划执行序号搜索时无意中依赖于特定于区域性的行为。If you use functions like string.IndexOf(string) without calling the overload that takes a StringComparison argument, you might intend to perform an ordinal search, but instead you inadvertently take a dependency on culture-specific behavior. 由于 NLS 和 ICU 在其语言比较器中实现的逻辑有所不同,因此 string.IndexOf(string) 等方法的结果可能会返回意外的值。Since NLS and ICU implement different logic in their linguistic comparers, the results of methods like string.IndexOf(string) can return unexpected values.

即使在全球化功能并非总是处于活动状态的地方,也会出现这类问题。This can manifest itself even in places where you aren't always expecting globalization facilities to be active. 例如,根据当前运行时,下面的代码可能会生成不同的答案。For example, the following code can produce a different answer depending on the current runtime.

string s = "Hello\r\nworld!";
int idx = s.IndexOf("\n");
Console.WriteLine(idx);

// The snippet prints:
//
// '6' when running on .NET Framework (Windows)
// '6' when running on .NET Core 2.x - 3.x (Windows)
// '-1' when running on .NET 5 (Windows)
// '-1' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
// '6' when running on .NET Core 2.x or .NET 5 (in invariant mode)

防范意外行为Guard against unexpected behavior

本节提供了处理 .NET 5.0 中的意外行为更改的两种方法。This section provides two options for dealing with unexpected behavior changes in .NET 5.0.

启用代码分析器Enable code analyzers

代码分析器可以检测可能存在错误的调用站点。Code analyzers can detect possibly buggy call sites. 为了帮助防范任何意外行为,建议在项目中启用 .NET Compiler Platform (Roslyn) 分析器。To help guard against any surprising behaviors, we recommend enabling .NET compiler platform (Roslyn) analyzers in your project. 该分析器有助于标记在计划使用序号比较器时可能无意中使用语言比较器的代码。The analyzers help flag code that might inadvertently be using a linguistic comparer when an ordinal comparer was likely intended. 以下规则应有助于标记这些问题:The following rules should help flag these issues:

默认不启用这些特定规则。These specific rules aren't enabled by default. 若要启用它们并将任何冲突显示为生成错误,请在项目文件中设置以下属性:To enable them and show any violations as build errors, set the following properties in your project file:

<PropertyGroup>
  <AnalysisMode>AllEnabledByDefault</AnalysisMode>
  <WarningsAsErrors>$(WarningsAsErrors);CA1307;CA1309;CA1310</WarningsAsErrors>
</PropertyGroup>

以下代码片段显示生成相关代码分析器警告或错误的代码示例。The following snippet shows examples of code that produces the relevant code analyzer warnings or errors.

//
// Potentially incorrect code - answer might vary based on locale.
//
string s = GetString();
// Produces analyzer warning CA1310 for string; CA1307 matches on char ','
int idx = s.IndexOf(",");
Console.WriteLine(idx);

//
// Corrected code - matches the literal substring ",".
//
string s = GetString();
int idx = s.IndexOf(",", StringComparison.Ordinal);
Console.WriteLine(idx);

//
// Corrected code (alternative) - searches for the literal ',' character.
//
string s = GetString();
int idx = s.IndexOf(',');
Console.WriteLine(idx);

同样,在实例化已排序的字符串集合或对现有基于字符串的集合进行排序时,请指定显式比较器。Similarly, when instantiating a sorted collection of strings or sorting an existing string-based collection, specify an explicit comparer.

//
// Potentially incorrect code - behavior might vary based on locale.
//
SortedSet<string> mySet = new SortedSet<string>();
List<string> list = GetListOfStrings();
list.Sort();

//
// Corrected code - uses ordinal sorting; doesn't vary by locale.
//
SortedSet<string> mySet = new SortedSet<string>(StringComparer.Ordinal);
List<string> list = GetListOfStrings();
list.Sort(StringComparer.Ordinal);

还原到 NLS 行为Revert back to NLS behaviors

在 Windows 上运行 .NET 5 应用程序时,若要将其还原到早前的 NLS 行为,请按照 .NET 全球化和 ICU 中的步骤操作。To revert .NET 5 applications back to older NLS behaviors when running on Windows, follow the steps in .NET Globalization and ICU. 必须在应用程序级别设置此应用程序范围的兼容性开关。This application-wide compatibility switch must be set at the application level. 单个库不能选择加入或选择退出此行为。Individual libraries cannot opt-in or opt-out of this behavior.

提示

强烈建议启用 CA1307CA1309CA1310 代码分析规则,以帮助改进代码卫生和发现任何现有的潜在 bug。We strongly recommend you enable the CA1307, CA1309, and CA1310 code analysis rules to help improve code hygiene and discover any existing latent bugs. 有关详细信息,请参阅启用代码分析器For more information, see Enable code analyzers.

受影响的 APIAffected APIs

大多数 .NET 应用程序不会遇到因 .NET 5.0 中的更改而导致的任何意外行为。Most .NET applications won't encounter any unexpected behaviors due to the changes in .NET 5.0. 但是,由于受影响的 API 数量以及这些 API 对更广泛的 .NET 生态系统的基础性,你应知道 .NET 5.0 可能会引入不需要的行为或公开应用程序中已存在的潜在 bug。However, due to the number of affected APIs and how foundational these APIs are to the wider .NET ecosystem, you should be aware of the potential for .NET 5.0 to introduce unwanted behaviors or to expose latent bugs that already exist in your application.

受影响的 API 包括:The affected APIs include:

备注

这不是受影响的 API 的详尽列表。This is not an exhaustive list of affected APIs.

默认情况下,上述所有 API 都使用语言字符串搜索与比较,它们使用线程的当前区域性All of the above APIs use linguistic string searching and comparison using the thread's current culture, by default. 序号和语言搜索与比较中指明了语言和序号搜索与比较之间的区别。The differences between linguistic and ordinal search and comparison are called out in the Ordinal vs. linguistic search and comparison.

由于 ICU 实现语言字符串比较的方式与 NLS 不同,从早期版本的 .NET Core 或 .NET Framework 升级到 .NET 5.0 的基于 Windows 的应用程序,在调用受影响的 API 之一时可能会注意到,这些 API 开始表现出不同的行为。Because ICU implements linguistic string comparisons differently from NLS, Windows-based applications that upgrade to .NET 5.0 from an earlier version of .NET Core or .NET Framework and that call one of the affected APIs may notice that the APIs begin exhibiting different behaviors.

异常Exceptions

  • 如果 API 接受显式 StringComparisonCultureInfo 参数,则该参数将覆盖 API 的默认行为。If an API accepts an explicit StringComparison or CultureInfo parameter, that parameter overrides the API's default behavior.
  • 第一个参数类型为 charSystem.String 成员(例如 String.IndexOf(Char))使用序号搜索,除非调用方传递指定 CurrentCulture[IgnoreCase]InvariantCulture[IgnoreCase] 的显式 StringComparison 参数。System.String members where the first parameter is of type char (for example, String.IndexOf(Char)) use ordinal searching, unless the caller passes an explicit StringComparison argument that specifies CurrentCulture[IgnoreCase] or InvariantCulture[IgnoreCase].

若要详细分析每个 String API 的默认行为,请参阅默认搜索和比较类型部分。For a more detailed analysis of the default behavior of each String API, see the Default search and comparison types section.

序号和语言搜索与比较Ordinal vs. linguistic search and comparison

序号(也称为“非语言”)搜索与比较将字符串拆分为其单独的 char 元素,并执行逐字符搜索或比较。Ordinal (also known as non-linguistic) search and comparison decomposes a string into its individual char elements and performs a char-by-char search or comparison. 例如,字符串 "dog""dog"Ordinal 比较器下的比较结果为“相等”,因为这两个字符串由完全相同的字符序列组成。For example, the strings "dog" and "dog" compare as equal under an Ordinal comparer, since the two strings consist of the exact same sequence of chars. 但是,"dog""Dog"Ordinal 比较器下的比较结果为“不相等”,因为它们由不完全相同的字符序列组成。However, "dog" and "Dog" compare as not equal under an Ordinal comparer, because they don't consist of the exact same sequence of chars. 也就是说,大写 'D' 的码位 U+0044 出现在小写 'd' 的码位 U+0064 之前,因此 "dog" 排在 "Dog" 之前。That is, uppercase 'D''s code point U+0044 occurs before lowercase 'd''s code point U+0064, resulting in "dog" sorting before "Dog".

OrdinalIgnoreCase 比较器仍执行逐字符操作,但它在执行该操作时消除了大小写差异。An OrdinalIgnoreCase comparer still operates on a char-by-char basis, but it eliminates case differences while performing the operation. OrdinalIgnoreCase 比较器下,字符对 'd''D' 的比较结果为“相等”,字符对 'á''Á' 亦是如此。Under an OrdinalIgnoreCase comparer, the char pairs 'd' and 'D' compare as equal, as do the char pairs 'á' and 'Á'. 但非重音字符 'a' 与重音字符 'á' 的比较结果为“不相等”。But the unaccented char 'a' compares as not equal to the accented char 'á'.

下表提供了此操作的一些示例:Some examples of this are provided in the following table:

字符串 1String 1 字符串 2String 2 Ordinal 比较Ordinal comparison OrdinalIgnoreCase 比较OrdinalIgnoreCase comparison
"dog" "dog" equalequal equalequal
"dog" "Dog" 不等于not equal equalequal
"resume" "Resume" 不等于not equal equalequal
"resume" "résumé" 不等于not equal 不等于not equal

Unicode 还允许字符串具有多个不同的内存中表示形式。Unicode also allows strings to have several different in-memory representations. 例如,带锐音符的 e (é) 可以用两种方式表示:For example, an e-acute (é) can be represented in two possible ways:

  • 单个文本 'é' 字符(亦可写为 '\u00E9')。A single literal 'é' character (also written as '\u00E9').
  • 文本无重音 'e' 字符,后跟一个组合用重音修饰符字符 '\u0301'A literal unaccented 'e' character, followed by a combining accent modifier character '\u0301'.

这意味着下面的四个字符串的显示结果都为 "résumé",即使它们的组成部分有所不同。This means that the following four strings all result in "résumé" when displayed, even though their constituent pieces are different. 该字符串使用文本 'é' 字符或文本无重音 'e' 字符,以及组合用重音修饰符 '\u0301'The strings use a combination of literal 'é' characters or literal unaccented 'e' characters plus the combining accent modifier '\u0301'.

  • "r\u00E9sum\u00E9"
  • "r\u00E9sume\u0301"
  • "re\u0301sum\u00E9"
  • "re\u0301sume\u0301"

在序号比较器下,这些字符串相互比较后的结果都不是“相等”。Under an ordinal comparer, none of these strings compare as equal to each other. 这是因为它们都包含不同的基础字符序列,即使在屏幕上显示时,它们看起来都是相同的。This is because they all contain different underlying char sequences, even though when they're rendered to the screen, they all look the same.

执行 string.IndexOf(..., StringComparison.Ordinal) 操作时,运行时查找完全匹配的子字符串。When performing a string.IndexOf(..., StringComparison.Ordinal) operation, the runtime looks for an exact substring match. 结果如下所示:The results are as follows.

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'
Console.WriteLine("resume".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '1'

序号搜索与比较例程从来不受当前线程的区域性设置的影响。Ordinal search and comparison routines are never affected by the current thread's culture setting.

语言搜索与比较例程将字符串拆分为排序元素,并对这些元素执行搜索或比较。Linguistic search and comparison routines decompose a string into collation elements and perform searches or comparisons on these elements. 字符串的字符和其构成的排序元素之间不一定存在 1:1 映射。There's not necessarily a 1:1 mapping between a string's characters and its constituent collation elements. 例如,长度为 2 的字符串可能只包含单个排序元素。For example, a string of length 2 may consist of only a single collation element. 使用识别语言的方式比较两个字符串时,比较器会检查两个字符串的排序元素是否具有相同的语义含义,即使这两个字符串的文本字符并不相同。When two strings are compared in a linguistic-aware fashion, the comparer checks whether the two strings' collation elements have the same semantic meaning, even if the string's literal characters are different.

再次考虑字符串 "résumé" 及其四种不同的表示形式。Consider again the string "résumé" and its four different representations. 下表展示了拆分为其排序元素的每种表示形式。The following table shows each representation broken down into its collation elements.

StringString 作为排序元素As collation elements
"r\u00E9sum\u00E9" "r" + "\u00E9" + "s" + "u" + "m" + "\u00E9"
"r\u00E9sume\u0301" "r" + "\u00E9" + "s" + "u" + "m" + "e\u0301"
"re\u0301sum\u00E9" "r" + "e\u0301" + "s" + "u" + "m" + "\u00E9"
"re\u0301sume\u0301" "r" + "e\u00E9" + "s" + "u" + "m" + "e\u0301"

排序元素松散地对应于读取器视为单个字符或字符群集的字符串。A collation element corresponds loosely to what readers would think of as a single character or cluster of characters. 它在概念上类似于字形群集,但包含更大的字符串。It's conceptually similar to a grapheme cluster but encompasses a somewhat larger umbrella.

在语言比较器下,不需要完全匹配。Under a linguistic comparer, exact matches aren't necessary. 排序元素根据其语义含义进行比较。Collation elements are instead compared based on their semantic meaning. 例如,语言比较器对子字符串 "\u00E9""e\u0301" 的比较结果为“相等”,因为它们在语义上都是指“带锐音符修饰符的小写字母 e”。For example, a linguistic comparer treats the substrings "\u00E9" and "e\u0301" as equal since they both semantically mean "a lowercase e with an acute accent modifier." 这允许 IndexOf 方法匹配更大字符串中的子字符串 "e\u0301",该更大字符串包含语义上等效的子字符串 "\u00E9",如下面的代码示例中所示。This allows the IndexOf method to match the substring "e\u0301" within a larger string that contains the semantically equivalent substring "\u00E9", as shown in the following code sample.

Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e")); // prints '-1' (not found)
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e\u00E9")); // prints '1'
Console.WriteLine("\u00E9".IndexOf("e\u00E9")); // prints '0'

因此,如果使用语言比较,则两个不同长度的字符串的比较结果可能为“相等”。As a consequence of this, two strings of different lengths may compare as equal if a linguistic comparison is used. 调用方应注意,在这种情况下处理字符串长度时,不要使用特殊情况逻辑。Callers should take care not to special-case logic that deals with string length in such scenarios.

识别区域性搜索与比较例程是语言搜索与比较例程的一种特殊形式。Culture-aware search and comparison routines are a special form of linguistic search and comparison routines. 在识别区域性比较器下,排序元素的概念扩展为包含特定于指定区域性的信息。Under a culture-aware comparer, the concept of a collation element is extended to include information specific to the specified culture.

例如,在匈牙利字母中,如果两个字符 <dz> 连续出现,则它们被认为是与 <d> 或 <z> 不同的独特字母。For example, in the Hungarian alphabet, when the two characters <dz> appear back-to-back, they are considered their own unique letter distinct from either <d> or <z>. 这意味着,如果在字符串中出现 <dz>,则匈牙利语识别区域性比较器会将其视为单个排序元素。This means that when <dz> is seen in a string, a Hungarian culture-aware comparer treats it as a single collation element.

StringString 作为排序元素As collation elements 注解Remarks
"endz" "e" + "n" + "d" + "z" (使用标准语言比较器)(using a standard linguistic comparer)
"endz" "e" + "n" + "dz" (使用匈牙利语识别区域性比较器)(using a Hungarian culture-aware comparer)

使用匈牙利语识别区域性比较器时,字符串 "endz" 不以子字符串 "z" 结尾,因为 <\dz> 和 <\z> 被视为具有不同语义含义的排序元素。When using a Hungarian culture-aware comparer, this means that the string "endz" does not end with the substring "z", as <\dz> and <\z> are considered collation elements with different semantic meaning.

// Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU");
Console.WriteLine("endz".EndsWith("z")); // Prints 'False'

// Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine("endz".EndsWith("z")); // Prints 'True'

备注

  • 行为:语言和识别区域性比较器可以不时地进行行为调整。Behavior: Linguistic and culture-aware comparers can undergo behavioral adjustments from time to time. ICU 和较旧的 Windows NLS 功能都将更新,以考虑世界语言的变化。Both ICU and the older Windows NLS facility are updated to account for how world languages change. 有关详细信息,请参阅博客文章区域设置(区域性)数据改动For more information, see the blog post Locale (culture) data churn. 序号比较器的行为将永远不会发生更改,因为它执行严格的按位搜索和比较。The Ordinal comparer's behavior will never change since it performs exact bitwise searching and comparison. 但是,OrdinalIgnoreCase 比较器的行为可能会随着 Unicode 的增加而改变,以包含更多的字符集,并纠正现有大小写数据中的遗漏。However, the OrdinalIgnoreCase comparer's behavior may change as Unicode grows to encompass more character sets and corrects omissions in existing casing data.
  • 使用情况:比较器 StringComparison.InvariantCultureStringComparison.InvariantCultureIgnoreCase 是不能识别区域性的语言比较器。Usage: The comparers StringComparison.InvariantCulture and StringComparison.InvariantCultureIgnoreCase are linguistic comparers that are not culture-aware. 也就是说,这些比较器理解一些概念,例如,重音字符 é 具有多种可能的基础表示形式,且所有这些表示形式都应视为“相等”。That is, these comparers understand concepts such as the accented character é having multiple possible underlying representations, and that all such representations should be treated equal. 但不识别区域性的语言比较器不包含对 <dz> 区别于 <d> 或 <z> 的特殊处理,如上所示。But non-culture-aware linguistic comparers won't contain special handling for <dz> as distinct from <d> or <z>, as shown above. 它们也不能处理像德语 Eszett (ß) 这样的特殊字符。They also won't special-case characters like the German Eszett (ß).

.NET 还提供固定全球化模式。.NET also offers the invariant globalization mode. 此选择加入模式禁用处理语言搜索与比较例程的代码路径。This opt-in mode disables code paths that deal with linguistic search and comparison routines. 在此模式下,无论调用方提供何种 CultureInfoStringComparison 参数,所有操作都使用序号或 OrdinalIgnoreCase 行为。In this mode, all operations use Ordinal or OrdinalIgnoreCase behaviors, regardless of what CultureInfo or StringComparison argument the caller provides. 有关详细信息,请参阅全球化运行时配置选项.NET Core 全球化固定模式For more information, see Run-time configuration options for globalization and .NET Core Globalization Invariant Mode.

有关详细信息,请参阅比较 .NET 中的字符串的最佳做法For more information, see Best practices for comparing strings in .NET.

安全隐患Security implications

如果你的应用使用受影响的 API 进行筛选,我们建议启用 CA1307 和 CA1309 代码分析规则,以帮助查找可能无意中使用了语言搜索而不是序号搜索的位置。If your app uses an affected API for filtering, we recommend enabling the CA1307 and CA1309 code analysis rules to help locate places where a linguistic search may have inadvertently been used instead of an ordinal search. 下面这样的代码模式可能易受安全漏洞的攻击。Code patterns like the following may be susceptible to security exploits.

//
// THIS SAMPLE CODE IS INCORRECT.
// DO NOT USE IT IN PRODUCTION.
//
public bool ContainsHtmlSensitiveCharacters(string input)
{
    if (input.IndexOf("<") >= 0) { return true; }
    if (input.IndexOf("&") >= 0) { return true; }
    return false;
}

由于 string.IndexOf(string) 方法默认使用语言搜索,因此字符串可能包含文本 '<''&' 字符,且 string.IndexOf(string) 例程可能返回 -1,表示找不到搜索子字符串。Because the string.IndexOf(string) method uses a linguistic search by default, it's possible for a string to contain a literal '<' or '&' character and for the string.IndexOf(string) routine to return -1, indicating that the search substring was not found. 代码分析规则 CA1307 和 CA1309 标记此类调用站点,并警告开发人员存在潜在的问题。Code analysis rules CA1307 and CA1309 flag such call sites and alert the developer that there's a potential problem.

默认搜索和比较类型Default search and comparison types

下表列出了各种字符串和类似于字符串的 API 的默认搜索和比较类型。The following table lists the default search and comparison types for various string and string-like APIs. 如果调用方提供显式 CultureInfoStringComparison 参数,则该参数将优先于任何默认值。If the caller provides an explicit CultureInfo or StringComparison parameter, that parameter will be honored over any default.

APIAPI 默认行为Default behavior 注解Remarks
string.Compare CurrentCultureCurrentCulture
string.CompareTo CurrentCultureCurrentCulture
string.Contains OrdinalOrdinal
string.EndsWith OrdinalOrdinal (当第一个参数为 char 时)(when the first parameter is a char)
string.EndsWith CurrentCultureCurrentCulture (当第一个参数为 string 时)(when the first parameter is a string)
string.Equals OrdinalOrdinal
string.GetHashCode OrdinalOrdinal
string.IndexOf OrdinalOrdinal (当第一个参数为 char 时)(when the first parameter is a char)
string.IndexOf CurrentCultureCurrentCulture (当第一个参数为 string 时)(when the first parameter is a string)
string.IndexOfAny OrdinalOrdinal
string.LastIndexOf OrdinalOrdinal (当第一个参数为 char 时)(when the first parameter is a char)
string.LastIndexOf CurrentCultureCurrentCulture (当第一个参数为 string 时)(when the first parameter is a string)
string.LastIndexOfAny OrdinalOrdinal
string.Replace OrdinalOrdinal
string.Split OrdinalOrdinal
string.StartsWith OrdinalOrdinal (当第一个参数为 char 时)(when the first parameter is a char)
string.StartsWith CurrentCultureCurrentCulture (当第一个参数为 string 时)(when the first parameter is a string)
string.ToLower CurrentCultureCurrentCulture
string.ToLowerInvariant InvariantCultureInvariantCulture
string.ToUpper CurrentCultureCurrentCulture
string.ToUpperInvariant InvariantCultureInvariantCulture
string.Trim OrdinalOrdinal
string.TrimEnd OrdinalOrdinal
string.TrimStart OrdinalOrdinal
string == string OrdinalOrdinal
string != string OrdinalOrdinal

string API 不同,默认情况下,所有 MemoryExtensions API 都执行序号搜索与比较,但以下情况例外。Unlike string APIs, all MemoryExtensions APIs perform Ordinal searches and comparisons by default, with the following exceptions.

APIAPI 默认行为Default behavior 注解Remarks
MemoryExtensions.ToLower CurrentCultureCurrentCulture (当传递 null CultureInfo 参数时)(when passed a null CultureInfo argument)
MemoryExtensions.ToLowerInvariant InvariantCultureInvariantCulture
MemoryExtensions.ToUpper CurrentCultureCurrentCulture (当传递 null CultureInfo 参数时)(when passed a null CultureInfo argument)
MemoryExtensions.ToUpperInvariant InvariantCultureInvariantCulture

结果是,在将代码从使用 string 转换为使用 ReadOnlySpan<char>时,可能会无意中引入行为更改。A consequence is that when converting code from consuming string to consuming ReadOnlySpan<char>, behavioral changes may be introduced inadvertently. 相关示例如下。An example of this follows.

string str = GetString();
if (str.StartsWith("Hello")) { /* do something */ } // this is a CULTURE-AWARE (linguistic) comparison

ReadOnlySpan<char> span = s.AsSpan();
if (span.StartsWith("Hello")) { /* do something */ } // this is an ORDINAL (non-linguistic) comparison

解决此问题的建议方法是将显式 StringComparison 参数传递给这些 API。The recommended way to address this is to pass an explicit StringComparison parameter to these APIs. 代码分析规则 CA1307 和 CA1309 可帮助解决此问题。The code analysis rules CA1307 and CA1309 can assist with this.

string str = GetString();
if (str.StartsWith("Hello", StringComparison.Ordinal)) { /* do something */ } // ordinal comparison

ReadOnlySpan<char> span = s.AsSpan();
if (span.StartsWith("Hello", StringComparison.Ordinal)) { /* do something */ } // ordinal comparison

请参阅See also