.NET 5.0的奇妙字符串操作

惊了,还有这种操作

Posted by 恋 on January 2, 2021

.NET 5.0的string.Contaisstring.IndexOf的坑。
不知道干什么的话,用StringComparison.Ordinal就对了。

新建一个net5程序,运行以下代码:

using System;
class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello\r\nWorld".Contains("\nW"));
        Console.WriteLine("Hello\r\nWorld".IndexOf("\nW"));
    }
}

我们可以看到,这段代码输出的结果是

True
-1

好像有什么不对…

为什么明明包含但是IndexOf找不到而Contains还是True的!

更多测试

Windows 10 20H2 Build 19042.685, en-CA (LCID 4105) fx mono core3 net5
"encyclopædia".Contains("ae")
"encyclopædia".IndexOf("ae")
False 8 False 8 False 8 False -1
"Hello\r\nWorld".Contains("\n")
"Hello\r\nWorld".IndexOf("\n")
True 6 True 6 True 6 True-1
"Hello\r\nWorld".Contains('\n')
"Hello\r\nWorld".IndexOf('\n')
True 6 True 6 True 6 True-1
"encyclopædia".Contains("ae", StringComparison.Ordinal)
"encyclopædia".IndexOf("ae", StringComparison.Ordinal)
-1 False -1 False -1 False-1
"Hello\r\nWorld".Contains("\n", StringComparison.Ordinal)
"Hello\r\nWorld".IndexOf("\n", StringComparison.Ordinal)
6 True 6 True 6 True 6
"Hello\r\nWorld".Contains('\n', StringComparison.Ordinal)
"Hello\r\nWorld".IndexOf('\n', StringComparison.Ordinal)
  True 6 True 6 True 6
"encyclopædia".Contains("ae", StringComparison.InvariantCulture)
"encyclopædia".IndexOf("ae", StringComparison.InvariantCulture)
8 True 8 True8 False -1
"Hello\r\nWorld".Contains("\n", StringComparison.InvariantCulture)
"Hello\r\nWorld".IndexOf("\n", StringComparison.InvariantCulture)
6 True 6 True 6 False -1
"Hello\r\nWorld".Contains('\n', StringComparison.InvariantCulture)
"Hello\r\nWorld".IndexOf('\n', StringComparison.InvariantCulture)
  True 6 True 6 False -1

fx平台下的部分string.Containsstring.IndexOf没有对应的重载,因此无结果

看起来更不对了…

为什么不光有True -1的组合,还有好几个NET5变了的结果啊!

ICU和NLS

.NET比较语言用的是platform dependent的本机代码(native)库。在Windows下,这个功能是由NLS(National Language Support)提供的,而在其他平台则是ICU(International Components for Unicode)提供的。

自从.NET 5开始,在Windows下也开始使用ICU - 所以NLS和ICU的不同行为导致了一部分的变化…

例如说,"\r\n".IndexOf("\n")是-1。

那是因为根据设定,相邻的<CR><LF>不可分割,所以这个\n不能单独拿出来说

思考 那为什么Contains又可以?

因为string.Contains(string)默认对比的是二进制(StringComparison.Ordinal

顺便,fx是没有string.Contains(char)的,你试图这么做的话会得到IEnumerable<char>.Contains(char)

string.Contains(string)string.Contains(char)默认是StringComparison.Ordinal

string.IndexOf(string)默认是StringComparison.CurrentCulture

小结

不知道干什么的话,指定StringComparison.Ordinal就对了。

至少,当你在用IndexOf来搜索字符串中的\n时,加别的选项或不加不一定能找到,但加上这个选项总能找到(如果有的话)。

References

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

MSDN: .NET globalization and ICU / MSDN: .NET 全球化和 ICU

GitHub Issue #43736 dotnet/runtime: Breaking change with string.IndexOf(string) from .NET Core 3.0 -> .NET 5.0