C#/.net core “hello”.IndexOf(“\0”,2)中的坑

news/2024/10/7 4:35:44

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

"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、 如果可以尽量主动显示设置当前文化区域设置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/54509.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

最简!手把手带你完美删除Vmware虚拟机!

Vmware虚拟机最简完美删除教程你还在苦于无法完美删除Vmware虚拟机吗?你还在为自己千疮百孔的系统而烦恼吗?你还在为想要重做Vmware但没删干净各种报错而烦操吗?但今天之后这些问题都将不是问题!现在赶紧收藏吧!以备未来不时之需!接下来我们便一起来把电脑里的Vmware17删…

YuebonCore:基于.NET8开源、免费的权限管理及快速开发框架

前言 今天大姚给大家分享一款基于.NET8开源、免费(MIT License)功能强大的权限管理及快速开发框架,支持前后端分离,项目架构易于扩展,是中小企业的首选:YuebonNetCore。核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展,让 Web 开发更快速、简单,…

C++基于模板实现智能指针

某厂面试,当时反正是没写出来,估计是寄了,事后做个记录。 #include <iostream> #include <mutex> using namespace std;class ObjectElement { private:char *addr;int size;void release() {addr = nullptr;size = 0;}public:ObjectElement(int size) {cout<…

cf补题计划_Edu 162_E

E. Count Paths 题目简介乍一眼一看是一个很简单树上dp(实际上也是树上dp 第一看做法是考虑dp[i][j]表达为第i个节点的下面有多少个颜色为j的节点,且这个节点于i节点之间无其他的节点为j颜色,然后dp转移十分的简单,但是n是\(2\cdot10^5\),绝对超时 然后显然我们发现,一个节…

Operating Systems: Principles and Practice 2nd ed. Edition

https://recursivebooks.com/ Operating Systems: Principles and Practice 2nd ed. Editionby Thomas Anderson (Author), Michael Dahlin (Author)https://www.kea.nu/files/textbooks/ospp/

Vue 3 路由组件缓存keep-alive

Vue 3 路由组件缓存 Vue3 KeepAlive官方文档 1. keep-alive 基本介绍keep-alive 是 Vue 的内置组件,用于缓存动态组件或路由组件,避免组件被频繁销毁和重建,从而提高性能。 当组件被 keep-alive 包裹后,在路由切换时不会销毁组件,而是将其缓存起来。下次切换回来时,会直接…

全网最适合入门的面向对象编程教程:41 Python 常用复合数据类型-队列(FIFO、LIFO、优先级队列、双端队列和环形队列)

在 Python 中,队列(Queue)是一种常用的数据结构,用于按照特定的顺序存储和访问数据。队列的主要类型包括先进先出(FIFO)、后进先出(LIFO)、优先级队列、双端队列(Deque)和环形队列,每种队列在不同的应用场景中都有其独特的用途。全网最适合入门的面向对象编程教程:…

基于 INFINI Pizza 为 Hugo 静态站点添加搜索功能

INFINI Pizza 是 INFINI Labs 即将发布的一个基于 Rust 编写的搜索引擎(即将完全开源),目前已经完成基本的搜索能力,并且基于 INFINI Pizza 的核心引擎,提供了一个 WASM 版本的超轻量级内核,可以很方便的嵌入到各类应用系统,比如网站,尤其是静态站点或者小型的博客系统…