C#/.net core “hello”.IndexOf(“\0”,2)中的坑
先想想看,你认为下面代码返回值是多少?
"hello".IndexOf("", 2);
"hello".IndexOf("\0", 2);
"hello".IndexOf('\0', 2);
今天和大家分享关于.net core中与字符相关的一些奇怪问题。
首先我们先以.NET8目标框架做为测试环境。直接上代码:
using System.Reflection;
using System.Runtime.Versioning;
namespace TestNetCore
{
internal class Program
{
static void Main(string[] args)
{
var assembly = Assembly.GetExecutingAssembly();
var targetFramework = (TargetFrameworkAttribute?)Attribute.GetCustomAttribute(assembly, typeof(TargetFrameworkAttribute));
if (targetFramework != null)
{
Console.WriteLine($"目标框架: {targetFramework.FrameworkName}");
}
Console.WriteLine(@"""hello"".IndexOf("""", 2) 结果:" + "hello".IndexOf("", 2));
Console.WriteLine(@"""hello"".IndexOf(""\0"", 2) 结果:" + "hello".IndexOf("\0", 2));
Console.WriteLine(@"""hello"".IndexOf('\0', 2) 结果:" + "hello".IndexOf('\0', 2));
Console.ReadKey();
}
}
}
运行结果如下:

这与你设想的结果有差别吗?虽然只是一个方法,三行代码,但是这里面包含了很多知识点,下面我们就来具体聊聊为什么是这样的结果,底层逻辑是什么。
相信大多数人会有一下几个疑问:
① 为什么查找空字符串返回2
② 为什么查找“\0”也返回2
③ 为什么查找‘\0‘又返回-1
1、为什么"hello".IndexOf("", 2)返回2
下面我们一个一个来说,首先来说为什么"hello".IndexOf("", 2)返回2,这个问题比较简单,就是方法本身定义问题,可以查看官方文档有详细说明,如下图:

方法定义如此,如果查询的值为空字符串,则返回值为startIndex,即查找起始位置索引,因为"hello".IndexOf("", 2)的2表示从索引2处开始查找,所以此这行代码返回2。
2、为什么"hello".IndexOf("\0", 2)也返回2
虽然这里只是简单的把空字符串改成了“\0”,但是里的问题就比较复杂了,涉及到多个知识点,首先这里涉及到Unicode编码问题,当前文化设置问题,以及.NET全球化问题。
我们先回到这个问题,先来看看官方文档说明,如下图:

从这里面可以大胆猜测“\0”就是属于可忽略字符,并且如果查询的字符串包含了可忽略字符,则结果和移除该字符搜索等效,那么"hello".IndexOf("\0", 2)就等效与"hello".IndexOf("", 2),因此返回2也就顺利成章了。
首先我们猜测是正确的,“\0的确属于可忽略字符,这就是Unicode编码规范问题,而且不单单“\0“会有这样的问题,Unicdoe可忽略字符都会有这样的问题,比如”\u0010“、”\u001B“等。
Console.WriteLine(@"""hello"".IndexOf(""\u0010"", 2) 结果:" + "hello".IndexOf("\u0010", 2));
Console.WriteLine(@"""hello"".IndexOf(""\u001B"", 2) 结果:" + "hello".IndexOf("\u001B", 2));
执行效果如下

3、为什么"hello".IndexOf(‘\0’, 2)返回-1
这个答案在上面的官方文档说明中也可以看到蛛丝马迹,“在执行语言性的或区分区域性的比较时该字符不被考虑“,这句话是关键,说明IndexOf方法是会受当前文化设置影响的,虽然我们写的代码里没有看到相关当前文化设置,但是不代表没有,我们可以看下IndexOf相关的重载方法。

红框中StringComparison参数就可以设置当前文化。我们看看有哪些设置选项。

因为"hello".IndexOf("\0", 2)内部使用了StringComparison.CurrentCulture 而"hello".IndexOf(‘\0’, 2) 内部使用了StringComparison.Ordinal,就是因为CurrentCulture枚举值导致“在执行语言性的或区分区域性的比较时”\0“不被考虑“,被直接忽略了,而Ordinal枚举值不会有这样的问题,所以没有被忽略,所以"hello".IndexOf(‘\0’, 2)返回-1。
我们也可以直接调用IndexOf重载方法,指定StringComparison来达到我们想要的效果。
Console.WriteLine(@"""hello"".IndexOf(""\0"", 2, 3, StringComparison.CurrentCulture) 结果:" + "hello".IndexOf("\0", 2, 3, StringComparison.CurrentCulture));
Console.WriteLine(@"""hello"".IndexOf(""\0"", 2, 3, StringComparison.Ordinal) 结果:" + "hello".IndexOf("\0", 2, 3, StringComparison.Ordinal));
运行代码如下:

虽然原因找到了,但是我们再深入思考一下,为什么会有这样的差异呢?只有IndexOf方法有这样的问题吗?
要回答这个问题,就是我们上面提到的.NET全球化问题了。在 .NET 5 前,.NET 全球化 API 在不同的平台上使用不同的基础库。 在 Unix 上,API 使用 Unicode 国际组件 (ICU),在 Windows 上,API 使用 区域语言支持 (NLS)。 这导致在不同平台上运行应用程序时,在少数全球化 API 中存在一些行为差异。 但是以下方面存在明显的行为差异:区域性和区域性数据、字符串大小写、字符串排序和搜索、排序关键字、字符串规范化、国际化域名 (IDN) 支持、Linux 上的时区显示名称。
因此不单单IndexOf方法有这样的问题,下面这些API都有存在同样的问题:
System.String.Compare
System.String.EndsWith
System.String.IndexOf
System.String.StartsWith
System.String.ToLower
System.String.ToLowerInvariant
System.String.ToUpper
System.String.ToUpperInvariant
System.Globalization.TextInfo(大多数成员)
System.Globalization.CompareInfo(大多数成员)
System.Array.Sort(对字符串数组进行排序时)
System.Collections.Generic.List<T>.Sort()(当列表元素为字符串时)
System.Collections.Generic.SortedDictionary<TKey,TValue>(当键为字符串时)
System.Collections.Generic.SortedList<TKey,TValue>(当键为字符串时)
System.Collections.Generic.SortedSet<T>(当集包含字符串时)
这里面很多方法都是有多个重载方法的,而每个重载方法默认当前文化设置可能并不相同。因此大家在开发的时候一定要注意使用,一不小心肯能就好引起一些奇怪的问题,因此大家尽量自己手动指定当前文化设置。
下表列出一些方法其对应的默认行为。

注:当然如果调用方提供显式 CultureInfo 或 StringComparison 参数,则该参数将优先于任何默认值。
最后总结一下
1、 IndexOf对于Empty字符查找会返回开始查找索引startIndex,而不是我们想象中的-1;
2、 Unicode可忽略字符受StringComparison参数影响很大,会直接把相应字符直接忽略掉
3、 .NET全球化进程中,区域语言支持 (NLS)在向 Unicode 国际组件 (ICU)迁移是必然,因此我们在使用相关方法时一定要小心
4、 如果可以尽量主动显示设置当前文化区域设置
C#/.net core “hello”.IndexOf(“\0”,2)中的坑的更多相关文章
- ASP.NET CORE MVC 2.0 项目中引用第三方DLL报错的解决办法 - InvalidOperationException: Cannot find compilation library location for package
目前在学习ASP.NET CORE MVC中,今天看到微软在ASP.NET CORE MVC 2.0中又恢复了允许开发人员引用第三方DLL程序集的功能,感到甚是高兴!于是我急忙写了个Demo想试试,我 ...
- Asp.net Core 1.0.1升级到Asp.net Core 1.1.0 Preview版本发布到Windows Server2008 R2 IIS中的各种坑
Asp.net Core 1.0.1升级到Asp.net Core 1.1.0后,程序无法运行了 解决方案:在project.json中加入runtime节点 "runtimes" ...
- ASP.NET Core 3.0 WebApi中使用Swagger生成API文档简介
参考地址,官网:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/getting-started-with-swashbuckle?view ...
- NET Core 3.0 项目中使用 AutoFac
.net core 3.1 今天已正式发布,3.1跟3.0差别不是很大,主要是对 3.0一小部分修复和完善,最重要的是.NET Core 3.1是长期支持(LTS)版本,建议大家升级. .net co ...
- ASP.NET Core 5.0 MVC中的 Razor 页面 介绍
Razor 是一个用于将基于服务器的代码嵌入到网页中的标记语法. Razor语法由 Razor 标记.c # 和 HTML 组成. 通常包含 Razor 的文件的扩展名 cshtml Razor 语法 ...
- ASP.NET CORE MVC 2.0 如何在Filter中使用依赖注入来读取AppSettings,及.NET Core控制台项目中读取AppSettings
问: ASP.NET CORE MVC 如何在Filter中使用依赖注入来读取AppSettings 答: Dependency injection is possible in filters as ...
- ASP.NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式
前言 开发授权服务框架一般使用OAuth2.0授权框架,而开发Webapi的授权更应该使用OAuth2.0授权标准,OAuth2.0授权框架文档说明参考:https://tools.ietf.org/ ...
- [转]Could not load file or assembly 'System.Core, Version=2.0.5.0 和autofac冲突的问题
Could not load file or assembly 'System.Core, Version=2.0.5.0 和autofac冲突的问题 来源:http://www.cnblogs.co ...
- Could not load file or assembly 'System.Core, Version=2.0.5.0 和autofac冲突的问题
在部署到iis的时候会出现这个状况. 解决:下载安装这个补丁 http://support.microsoft.com/kb/2468871 http://www.microsoft.com/zh-c ...
- .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (二)
.net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (二) .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (一) 上一篇主 ...
随机推荐
- mac idea代码颜色修改、主题
下载样式包 下载样式 网址一:http://www.riaway.com/ 网址二:http://www.themesmap.com/theme.html?t=down 我使用的这几款 链接: htt ...
- 使用forEach循环异步方法,导致使用深拷贝时,得不到最新数据,控制台会打印出最新的数据
在使用forEach循环遍历一个数组,如果循环时有异步方法,会导致最终深拷贝得不到最新数据,但是控制台会打印最新的数据 `const arr = [ { name: "Jone", ...
- iOS开发基础135-Core Data
Objective-C (OC) 中使用 Core Data 是iOS应用开发中管理模型层对象的一种有效工具.Core Data 使用 ORM (对象关系映射) 技术来抽象化和管理数据.这不仅可以节省 ...
- CCF 有趣的数
问题描述: 试题编号: 201312-4 试题名称: 有趣的数 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 我们把一个数称为有趣的,当且仅当: 1. 它的数字只包含0, 1 ...
- 断点续传:使用java对大文件进行分块与合并
通常我们下载上传的视频文件比较大.虽然https协议没有规定上传文件大小的限制,但是网络的质量,电脑硬件的参差不齐可能会导致大文件快要上传完成的时候突然断网了要重新上传,非常影响用户体验.以此我们引入 ...
- layout文本相关
Textview t=findViewById(R.id.t); ONE设置文本内容: 在XML中android:text直接写 在java中setText()中修改 注意点1继承appcompata ...
- 【SpringBoot】04 初探YAML与配置
什么是YAML? https://www.cnblogs.com/mindzone/p/12849789.html 复合结构的语法 一个标配JavaBean public class Person { ...
- 【Node】下载安装(Linux)
不要使用源码包安装!!!编译时间太长!! 不要使用源码包安装!!!编译时间太长!! 不要使用源码包安装!!!编译时间太长!! 使用Node源码包安装 这里使用的是源码包安装 Node官网地址:也不是官 ...
- 【Java】API 时区ID类
主要是用以获取时区ID @Test void contextLoads() { ZoneId zoneId = ZoneId.systemDefault(); // System.out.printl ...
- Linux统计文件目录下文件的数目命令
Linux下有三个命令:ls.grep.wc.通过这三个命令的组合可以统计目录下文件及文件夹的个数. 1.ls -l | grep "^-" | wc -l:统计当前目录下文件的个 ...