CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
C#中切片语法糖,我再也不羡慕Python了
0
Csharp
小笨蛋
发布于:2021年07月08日
更新于:2021年07月08日
145
#custom-toc-container
### 一、背景 #### 1、讲故事 昨天在 github 上准备找找 C# 9又有哪些新语法糖可以试用,不觉在一个文档上看到一个很奇怪的写法: `foreach (var item in myArray[0..5])` 哈哈,熟悉又陌生,玩过`python`的朋友对这个 [0..5] 太熟悉不过了,居然在 C# 中也遇到了,开心哈,看了下是 C# 8 的新语法,讽刺讽刺,8 都没玩熟就搞 9 了,我的探索欲比较强,总想看看这玩意底层是由什么支撑的。 ### 二、.. 语法糖的用法 从前面介绍的 `myArray[0..5]` 语义上也能看出,这是一个切分`array`的操作,那到底有几种切分方式呢?下面一个一个来介绍,为了方便演示,我先定义一个数组,代码如下: ```csharp var myarr = new string[] {"10", "20", "30", "40", "50", "60", "70", "80", "90", "100"}; ``` #### 1、提取 arr 前3个元素 如果用 `linq` 的话,可以用 `Take(3)`,用切片操作的话就是 [0..3], 代码如下: ```csharp static void Main(string[] args) { var myarr = new string[] {"10", "20", "30", "40", "50", "60", "70", "80", "90", "100"}; //1. 获取数组 前3个元素 var query1 = myarr[0..3]; var query2 = myarr.Take(3).ToList(); Console.WriteLine($"query1={string.Join(",", query1)}"); Console.WriteLine($"query2={string.Join(",", query2)}"); } ``` ![图片alt](/uploads/images/20210708/223001-e23bbde37d7a46c88eae4781ddf927d9.png ''图片title'') #### 2、提取 arr 最后三个元素 这个怎么提取呢?在 `python` 中直接用 -3 表示就可以了,在C# 中需要用 ^ 来表示从末尾开始,代码如下: ```csharp static void Main(string[] args) { var myarr = new string[] {"10", "20", "30", "40", "50", "60", "70", "80", "90", "100" }; //1. 获取数组 最后3个元素 var query1 = myarr[^3..]; var query2 = myarr.Skip(myarr.Length - 3).ToList(); Console.WriteLine($"query1={string.Join(",", query1)}"); Console.WriteLine($"query2={string.Join(",", query2)}"); } ``` ![图片alt](/uploads/images/20210708/223028-003a2a9529484dfa826288f61c071b6b.png ''图片title'') #### 3、提取 array 中index = 4,5,6 的三个位置元素 用 `linq` 的话,就需要使用 `Skip + Take` 双组合,如果用切片操作的话就太简单了。 ```csharp static void Main(string[] args) { var myarr = new string[] {"10", "20", "30", "40", "50", "60", "70", "80", "90", "100" }; //1. 获取数组 中 index=4,5,6 三个位置的元素 var query1 = myarr[4..7]; var query2 = myarr.Skip(4).Take(3).ToList(); Console.WriteLine($"query1={string.Join(",", query1)}"); Console.WriteLine($"query2={string.Join(",", query2)}"); } ``` 从上面的切割区间 [4..7] 的输出结果来看,这是一个 左闭右开 的区间,所以要特别注意一下。 #### 4、获取 array 中倒数第三和第二个元素 从要求上来看就是获取元素 80 和 90,如果你理解了前面的两个用法,我相信这个你会很快的写出来,代码如下: ```csharp static void Main(string[] args) { var myarr = new string[] {"10", "20", "30", "40", "50", "60", "70", "80", "90", "100" }; //1. 获取 array 中倒数第三和第二个元素 var query1 = myarr[^3..^1]; var query2 = myarr.Skip(myarr.Length - 3).Take(2).ToList(); Console.WriteLine($"query1={string.Join(",", query1)}"); Console.WriteLine($"query2={string.Join(",", query2)}"); } ``` ![图片alt](/uploads/images/20210708/223138-49610bd7930d430eb0f8b849e6bb362d.png ''图片title'') ### 三、探究原理 通过前面 4 个例子,我想大家都知道怎么玩了,接下来就是看看到底内部是用什么做支撑的,这里使用 DnSpy 去挖挖看。 #### 1、从 myarr[0..3] 看起 用 dnspy 反编译代码如下: ```csharp //编译前 var query1 = myarr[0..3]; //编译后: string[] query = RuntimeHelpers.GetSubArray
(myarr, new Range(0, 3)); ``` 从编译后的代码可以看出,原来获取切片的 `array` 是调用 `RuntimeHelpers.GetSubArray` 得到了,然后我简化一下这个方法,代码如下: ```csharp public static T[] GetSubArray<[Nullable(2)] T>(T[] array, Range range) { ValueTuple
offsetAndLength = range.GetOffsetAndLength(array.Length); int item = offsetAndLength.Item1; int item2 = offsetAndLength.Item2; T[] array3 = new T[item2]; Buffer.Memmove(Unsafe.As(array3.GetRawSzArrayData()), Unsafe.Add(Unsafe.As(array.GetRawSzArrayData()), item), (ulong)item2); return array3; } ``` 从上面代码可以看到,最后的子`array` 是由 `Buffer.Memmove` 完成的,但是给 子`array`的切割位置是由 `GetOffsetAndLength` 方法实现,继续追一下代码: ```csharp public readonly struct Range : IEquatable { public Index Start { get; } public Index End { get; } public Range(Index start, Index end) { this.Start = start; this.End = end; } public ValueTuple
GetOffsetAndLength(int length) { Index start = this.Start; int num; if (start.IsFromEnd) { num = length - start.Value; } else { num = start.Value; } Index end = this.End; int num2; if (end.IsFromEnd) { num2 = length - end.Value; } else { num2 = end.Value; } return new ValueTuple
(num, num2 - num); } } ``` 看完上面的代码,你可能有两点疑惑: - start.IsFromEnd 和 end.IsFromEnd 是什么意思。 其实看完上面代码逻辑,你就明白了,IsFromEnd 表示起始点是从左开始还是从右边开始,就这么简单。 - 2我并没有看到 start.IsFromEnd 和 end.IsFromEnd 是怎么赋上值的。 在 Index 类的构造函数中,取决于上一层怎么去 new Index 的时候塞入的 true 或者 false,如下代码: ![图片alt](/uploads/images/20210708/223412-b504673667384c7fafd62fba571c38c3.png ''图片title'') 这个例子的流程大概是:`new Range(1,3) -> operator Index(int value) -> FromStart(value) -> new Index(value) `,可以看到最后在 new 的时候并没有对可选参数赋值。 #### 2、探究 myarr[^3..] 刚才的例子是没有对可选参数赋值,那看看本例是不是 `new Index` 的时候赋值了? ```csharp //编译前: var query1 = myarr[^3..]; //编译后: string[] query = RuntimeHelpers.GetSubArray
(myarr, Range.StartAt(new Index(3, true))); ``` 看到没有,这一次 new Index 的时候,给了 IsFromEnd = true , 表示从末尾开始计算,大家再结合刚才的 GetOffsetAndLength 方法,我想这逻辑你应该理顺了吧。 ### 四、总结 总的来说这个切片操作太实用了,作用于 arr 可以大幅度减少对 skip & take 的使用,作用于string也可以大幅减少SubString的使用,如:"12345"[1..3] -> "12345".Substring(1, 2),嘿嘿,厉害了吧! 还是C# 大法牛
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024