一:背景
講故事昨天在 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 ;
提取 arr 前3個元素
如果用 linq 的話,可以用 take(3),用切片操作的話就是 [0…3], **如下:
static void main(string[
] args);//
1. 獲取陣列 前3個元素
提取 arr 最後三個元素
這個怎麼提取呢?在 python 中直接用 -3 表示就可以了,在c# 中需要用 ^ 來表示從末尾開始,**如下:
static void main(string[
] args);//
1. 獲取陣列 最後3個元素
var query1 = myarr[^3
..];
var query2 = myarr.skip(myarr.length -3)
.tolist();
console.writeline($"query1=");
console.writeline($"query2=");
}
提取 array 中index = 4,5,6 的三個位置元素
用 linq 的話,就需要使用 skip + take 雙組合,如果用切片操作的話就太簡單了。。。
static void main(string[
] args);//
1. 獲取陣列 中 index=4,
5,6 三個位置的元素
var query1 = myarr[4.
.7]; var query2 = myarr.skip(4)
.take(3)
.tolist();
console.writeline($"query1=");
console.writeline($"query2=");
}
從上面的切割區間 [4…7] 的輸出結果來看,這是乙個 左閉右開 的區間,所以要特別注意一下。
獲取 array 中倒數第三和第二個元素
從要求上來看就是獲取元素 80 和 90,如果你理解了前面的兩個用法,我相信這個你會很快的寫出來,**如下:
static void main(string[
] args);//
1. 獲取 array 中倒數第三和第二個元素
var query1 = myarr[^3
..^1
];var query2 = myarr.skip(myarr.length -3)
.take(2)
.tolist();
console.writeline($"query1=");
console.writeline($"query2=");
}
三. **原理
通過前面 4 個例子,我想大家都c#教程知道怎麼玩了,接下來就是看看到底內部是用什麼做支撐的,這裡使用 dnspy 去挖挖看。
從 myarr[0…3] 看起
用 dnspy 反編譯**如下:
//編譯前
var query1 = myarr[0.
.3];//編譯後:
string[
] query = runtimehelpers.getsubarray
(myarr, new range(0,
3));
從編譯後的**可以看出,原來獲取切片的 array 是呼叫 runtimehelpers.getsubarray 得到了,然後我簡化一下這個方法,**如下:
public static t[
] getsubarray<
[nullable(2)
] t>
(t array, range range
)
從上面**可以看到,最後的 子array 是由 buffer.memmove 完成的,但是給 子array 的切割位置是由 getoffsetandlength 方法實現,繼續追一下**:
public readonly struct range : iequatable
public index end
public range(index start, index end)
public valuetuple<
int,
int> getoffsetandlength(
int length)
else
index end = this.end;
int num2;
if(end.isfromend)
else
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 的時候並沒有對可選引數賦值。
** myarr[^3…]
剛才的例子是沒有對可選引數賦值,那看看本例是不是 new index 的時候賦值了?
//編譯前:
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# **??
C 中居然也有切片語法糖,太厲害了
昨天在 github 上準備找找 c 9 又有哪些新語法糖可以試用,不覺在乙個文件上看到乙個很奇怪的寫法 foreach var item in myarray 0.5 哈哈,熟悉又陌生,玩過python的朋友對這個 0.5 太熟悉不過了,居然在 c 中也遇到了,開心哈,看了下是 c 8 的新語法,...
C 中建構函式居然是可以直接呼叫的
如下 include class a a int x a x b x 1 a int geta int getb private int a int b int main 經過編譯和執行之後,結果如下 呼叫第乙個建構函式 呼叫第二個建構函式 呼叫析構函式 5 6 呼叫析構函式 首先要知道 一 c 編...
C 中建構函式居然是可以直接呼叫的
如下 include class a a int x a x b x 1 a int geta int getb private int a int b int main 經過編譯和執行之後,結果如下 呼叫第乙個建構函式 呼叫第二個建構函式 呼叫析構函式 5 6 呼叫析構函式 首先要知道 一 c 編...