本文參考Roslyn項目中的Issue:#118。
1. C# 7.0 新特性1: 基于Tuple的“多”傳回值方法
2. C# 7.0 新特性2: 本地方法
3. C# 7.0 新特性3: 模式比對
4. C# 7.0 新特性4: 傳回引用
C#早在最初的發行版C# 1.0中(2002年1月),就借鑒并延續了C/C++中指針參數,原生允許将值類型資料的引用(指針)通過标記ref參數的形式,傳遞到方法體中。
但對于方法内的值類型引用,該如何以引用的方式傳回,卻一直以來沒有一個非常完美的解決方案,盡管這種用例非常少見。
提一個簡單的問題,我們需要擷取三個int中的最大值的引用。
我們照慣例,回顧下C#7.0之前的做法:
C/C++指針
我們回歸到C/C++中,這個問題沒有什麼好争議的,實作起來會很理所應當的是這樣的:
1 int* Max(int* first, int* second, int* third) {
2 int* max = *first > *second ? first : second;
3 return *max > *third ? max : third;
4 }
5 ....
6 int a = 1, b = 2, c = 3;
7 int* max = Max(&a, &b, &c);
8 *max = 4; // c == 4;
下面我們思考一下C#中怎麼合理的翻譯這段代碼。
/unsafe 指令
可能有的童鞋看到C/C++指針,已經想到了.NET編譯指令中,開啟/unsafe指令,它允許C#直接通路記憶體。的确,隻要在項目中勾選“Allow unsafe code”。

就可以通過下面這種幾乎和C/C++中一緻方式來做到:
1 unsafe static int* Max(int* first, int* second, int* third)
2 {
3 int* max = *first > *second ? first : second;
4 return *max > *third ? max : third;
5 }
6 ....
7 int a = 1, b = 2, c = 3;
8 unsafe
9 {
10 int* max = Max(&a, &b, &c);
11 *max = 4; // c == 4
12 }
但unsafe并不是C# 推薦使用的,它繞過了CLR的記憶體安全機制,指針的不安全濫用會被允許,容易使你的指針指到各種非預期的目标,比如允許通路已經傳回(被釋放)的調用棧(call stack),我們來做一個實驗。
1 unsafe static int* GetRef()
2 {
3 //Some codes
4 int i = 4;
5 return &i;
6 }
7 unsafe static void Main(string[] args)
8 {
9 int* num = GetRef();
10 Console.WriteLine(*num); // 4
11 //Some codes
12 Console.WriteLine(*num); // 不可預期
13 }
這是非常典型的一種錯誤,當GetRef()的調用傳回後,它的調用堆棧被釋放,我們嘗試擷取它本地的引用(num)時,如果GetRef遺留在記憶體的棧結構僥幸沒有被重新配置設定,我們依然可以擷取到。
但正常情況下,我們的邏輯一旦需要做一些其它處理(包括第一次Console.WriteLine()的調用本身),num所在的這塊不安全記憶體自然會被覆寫。
雖然這是一段本身錯誤的代碼,但站在語言層面,并沒有做任何完全可以做的規避。(C/C++中同樣存在這個問題)
傳回模型對象
當然,其實C#6.0及以前,我們還有一種比較常見的方案:将有必要傳回引用的值類型封裝在一個寄宿模型類中。
由于對象以引用heap的位址傳遞,引用目标不在調用棧(call stack)上,不會由于函數傳回而被釋放。
1 static HostModel Max(HostModel first, HostModel second, HostModel third)
2 {
3 HostModel max = first.Value > second.Value ? first : second;
4 return max.Value > third.Value ? max : third;
5 }
這種類似做法被廣泛應用在Model傳遞,DTO等場景中,無可厚非。。
但是如果在性能要求敏感,且資料和邏輯結構簡單的場景下,為一個簡單資料憑空多了一組裝箱和拆箱動作,以對象形式在heap中申請本沒有必要的記憶體,是一種非常浪費和奢侈的做法。
引用傳回
C#7.0 中引入了引用傳回(ref return)的概念,允許C#方法中傳回一個值類型的引用。
Issue:#118。中給出了下面的例子:
1 static ref int Max(ref int first, ref int second, ref int third)
2 {
3 ref int max = first > second ? ref first : ref second;
4 return max > third ? ref max : ref third;
5 }
6 …
7 int a = 1, b = 2, c = 3;
8 Max(ref a, ref b, ref c) = 4;
9 Debug.Assert(a == 1); // true
10 Debug.Assert(b == 2); // true
11 Debug.Assert(c == 4); // true
這樣,我們通過C#7.0,能直接将調用棧(call stack)上的引用傳回。
并且,對于體積較大的結構體(struct),傳回引用比傳遞結構值要快很多,因為結構體的指派會對整個結構進行拷貝。
另外需要注意的是,ref return的引用,在語言層面附加規則,不允許傳回方法内的局部變量的引用,換句話說,被傳回的堆棧位址,必須低于目前方法的入口位址。
總結
我們從另一個側面看這個feature,其實是對性能要求極緻情況下出現的考慮,對于目前大多數的.NET應用中,其實用例非常局限,也并非以往.NET側重的方面。。
但是Roslyn項目在C#7.0設計初期就加入這個feature,是否隐含了更長遠的考量?
我們再看看微軟最近的新聞就不難了解了,本月初(6月1日)微軟在北京舉辦的開發者峰會上,Satya Nadella宣布建立物聯網實驗室,峰會上還釋出了微軟的IoT套件。
近期微軟還釋出了Windows的IoT版本(Windows IoT),剛剛釋出的.NET Core也允許跑在裝有Windows IoT 的 Raspberry PI(樹莓派)等裝置上。
在這些對惜記憶體如金的端裝置上,C#想要有一席用武之地,不可避免的需要一改以往對記憶體的任性的一些設計,也就可以了解了。這或許是C#7.0加入ref return的一個重要的原因。
本文連結:http://www.cnblogs.com/ylvict/p/5633480.html (轉載請注明)
目前(2016年7月)C#7.0還未正式釋出,大家如果想體驗部分特性,可以去下載下傳VS15預覽版,最終釋出的文法可能和本文中提及的有所不同,最新動态請大家關注Roslyn項目。