先想想看,你认为下面代码返回值是多少?

"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)中的坑的更多相关文章

  1. 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想试试,我 ...

  2. 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" ...

  3. ASP.NET Core 3.0 WebApi中使用Swagger生成API文档简介

    参考地址,官网:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/getting-started-with-swashbuckle?view ...

  4. NET Core 3.0 项目中使用 AutoFac

    .net core 3.1 今天已正式发布,3.1跟3.0差别不是很大,主要是对 3.0一小部分修复和完善,最重要的是.NET Core 3.1是长期支持(LTS)版本,建议大家升级. .net co ...

  5. ASP.NET Core 5.0 MVC中的 Razor 页面 介绍

    Razor 是一个用于将基于服务器的代码嵌入到网页中的标记语法. Razor语法由 Razor 标记.c # 和 HTML 组成. 通常包含 Razor 的文件的扩展名 cshtml Razor 语法 ...

  6. ASP.NET CORE MVC 2.0 如何在Filter中使用依赖注入来读取AppSettings,及.NET Core控制台项目中读取AppSettings

    问: ASP.NET CORE MVC 如何在Filter中使用依赖注入来读取AppSettings 答: Dependency injection is possible in filters as ...

  7. ASP.NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式

    前言 开发授权服务框架一般使用OAuth2.0授权框架,而开发Webapi的授权更应该使用OAuth2.0授权标准,OAuth2.0授权框架文档说明参考:https://tools.ietf.org/ ...

  8. [转]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 ...

  9. 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 ...

  10. .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (二)

    .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (二) .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (一) 上一篇主 ...

随机推荐

  1. mac电脑好用的工具总结

    制作gif:https://gfycat.com/gifbrewery 制作gif(超级好用,制作速度快,压缩图片小):https://www.cockos.com/licecap/ 解压工具:htt ...

  2. elementplus弹窗可拖拽draggable,点击空白处不消失close-on-click-modal,modal是否去掉遮罩层

    <el-dialog :modal="false" v-model="dialogVisible" title="" width=&q ...

  3. [UE] 关于ue5中制作流日志记录

    UE5目前根据现有功能,配合Quixel Bridge可以做到地编和一些简单的动画,实现完整的游戏,但是目前随着版本的迭代,流程的定制需要更新 ControlRig方便在UE中做动画的,模拟动画等,U ...

  4. OpenGL之ShadowMap

    流程:先创建一个RenderTexture,然后用灯光的视口渲染. 然后切换到正常相机,进行渲染,使用RenderTexture中的深度或者颜色纹理,然后还原当前顶点在灯光中的深度,两者对比,比缓存中 ...

  5. Python threading实现多线程 基础篇

    讲多线程前,先要了解什么是进程,什么是线程,已经知道的请略过. 一.进程与线程: 进程是资源分配的最小单位,一个程序至少有一个进程. 线程是程序执行的最小单位,一个进程至少有一个线程. 进程都有自己独 ...

  6. [SDR] GNU Radio 系列教程 —— GNU Radio TX PDU (发送数据包操作)的基础知识(超全)

    目录 1 PDU 概述 2 Demo 详解 2.1 Random PDU Generator 2.2 Async CRC32 2.3 Protocol Formatter (Async) 2.4 将 ...

  7. MySQL常用语句(经常容易忘记)

    MySQL常用语句 一.连接MySQL 格式: mysql -h <主机地址> -u<用户名> -p<用户密码> --port=<端口号> 1.例1:连 ...

  8. SSH Exporter:基于Prometheus的远程系统性能监控神器

    SSH Exporter English | 中文 介绍 SSH Exporter 是一个基于 Prometheus 规范的监控工具,通过 SSH 协议远程收集目标服务器的系统性能数据,如 CPU 使 ...

  9. 【Vue】04 模块化开发演变

    JS最初的目的是用来做表单验证和动画效果,可以让网页更加生动. 但是使用Ajax,前后端分离,页面承担了更多的事情,JS的代码量暴增,代码管理维护逐渐困难 我们需要将JS代码抽取出来,模块化处理, 但 ...

  10. nginx+tomcat部署均衡+虚拟IP配置

    一.配置java环境 更新数据源 sudo apt update 安装java sudo apt install openjdk-java-8-jdk 查看是否安装成功 java -version 二 ...