探索C# 8新语法糖:如[0..5]切分array的奇妙用法及底层支撑
[id_1[id_254453300]56716716]
![探索C# 8新语法糖:如[0..5]切分array的奇妙用法及底层支撑 探索C# 8新语法糖:如[0..5]切分array的奇妙用法及底层支撑](http://blog.seo.xz.cn/zb_users/upload/sanimg/e4c8f48461fc6ef5776228309e58391f.png)
昨天在github上打算找找C# 9还有哪些新语法糖能试用,不经意间在一个文档上看到一种很奇怪的写法,foreach (var item in myArray[0..5]) 哈哈,既熟悉又陌生,玩过python的朋友对这个[0..5]再熟悉不过了,没想到在C#中也碰到了,真开心,查看后发现是C# 8的新语法,真有意思,8都没掌握好就搞9了,我探索欲较强,总想着看看这东西底层是靠什么支撑的。
二:.. 语法糖的用法
从前面介绍的myArray[0..5]语义中也能够看出,这是一项切分array的操作,那么究竟存在几种切分方式呢?下面逐一进行介绍,为了便于演示,我先定义一个数组,代码如下:
var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };
1. 提取 arr 前3个元素
要是使用linq,能够用Take(3),要是采用切片操作,那便是[0..3],代码如下:
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();
控制台写入一行内容(这里原句不完整,推测是进行输出操作相关的代码片段,按照要求进行了合理补充和改写)。$"query1={string.Join(",", query1)}");
Console.WriteLine($"query2={string.Join(",", query2)}");
}
2. 提取 arr 最后三个元素
这个要如何提取呢,在python里直接用 -3来表示就行,在C#里得用 ^来表示从末尾开始,代码如下:
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请你补充完整“myarr.Length -”后面的内容,以便我能准确为你改写。3).ToList();
Console.WriteLine($"query1={string.Join(",", query1)}");
Console.WriteLine($"query2={string.Join(",", query2)}");
}
提取array中的元素,这些元素位于index等于4的位置,位于index等于5的位置,位于index等于6的位置,共三个位置的元素 。
要是使用linq,那就得运用Skip和Take这两个组合,要是采用切片操作,那就会简单得多。。。
static void Main(string[] args)
{
var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };
获取数组中的元素,这些元素位于index为4的位置,位于index为5的位置,位于index为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,要是你理解了前面的两个用法,我相信这个你能够很快写出来,代码如下:
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)}");
}
三. 探究原理
通过前面4个例子,我想大家都已明白玩法,接下来要看其内部究竟以什么作为支撑,在此使用DnSpy进行探究 。
1. 从 myarr[0..3] 看起
用 dnspy 反编译代码如下:
//编译前
var query1 = myarr[0..3];
//编译后:
stringquery等于RuntimeHelpers获取子数组 。<string>(myarr, new Range(0, 3));
从编译后的代码能够看出,原本获取切片的array是通过调用RuntimeHelpers.GetSubArray获得的,之后我对这个方法进行了简化,代码如下:
public static T[] GetSubArray<[Nullable(2)] T>(T[] array, Range range)
{
ValueTuple<int, int获取偏移量和长度,偏移量和长度等于范围获取数组长度后的偏移量和长度 。
int将“item”赋值为“offsetAndLength.Item1”的值 。
int将“item2”赋值为“offsetAndLength.Item2”的值 。
T[] array3 = new T[item2];
Buffer.Memmove(Unsafe.As<byte请你提供完整的句子内容,以便我能准确按照要求进行改写。仅这一句“T>(array3.GetRawSzArrayData()), Unsafe.Add”信息不完整,无法独立完成改写 。(Unsafe.As<byte请提供完整的句子以便准确改写。仅这部分内容无法进行有效改写。ulong)item2);
return array3;
}
从上面代码能够看到,最后的子array是由Buffer.Memmove完成的,不过给子array的切割位置是由GetOffsetAndLength方法实现的,接着继续追一下代码:
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<int, int> 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等于长度减去结束值 。
}
else
{
num2的值为end.Value 。
}
return new ValueTuple<int, int>(num, num2 - num);
}
}
看完上面的代码,你可能有两点疑惑:
start.IsFromEnd是什么意思,end.IsFromEnd是什么意思。
实际上,当你看完上面代码的逻辑后,你就会明白,IsFromEnd所表达的意思是,起始点究竟是从左边开始,还是从右边开始,就是如此简单。
我没有看到,start.IsFromEnd是如何被赋上值的,end.IsFromEnd又是如何被赋上值的。
在Index类的构造函数里,这取决于上一层以何种方式在new Index时塞入true或者false,代码如下:
这个例子的流程大概是:首先是 new Range(1,3) ,接着是 operator Index(int value) ,然后是 FromStart(value) ,最后是 new Index(value) ,可以看到最后在 new 的时候并没有对可选参数赋值。
2. 探究 myarr[^3..]
刚才的例子没有对可选参数进行赋值,那现在看看这个例子,在创建 new Index 的时候有没有进行赋值呢?
//编译前:
var query1 = myarr[^3..];
//编译后:
string[] query = RuntimeHelpers.GetSubArray<string>(myarr, Range.StartAt(new Index(3, true)));
看到没有,这一次进行新索引操作的时候,给了一个表示从末尾开始计算的参数,其值为IsFromEnd = true,大家再结合刚才的GetOffsetAndLength方法,我想这逻辑你应该理顺了吧。
四:总结
总的来说这个切片操作非常实用,它作用于arr时能够大幅度减少对skip和take的使用,作用于string时也能够大幅减少SubString的使用,例如:"12345"[1..3] 等同于 "12345".Substring(1, 2),嘿嘿,很厉害吧!还是C#厉害