準備工作:
Visual Studio 2019 Preview版本中并沒有包含所有的C# 8.0的新功能,但目前也有一些可以試用了。在開始之前,需要進行入兩項設置:
- 將Framework設置為.net core 3.0
- 將C#語法設置為8.0
也可以直接編輯.csproj文件,修改TargetFramework和LangVersion為如下形式:
???? <TargetFramework>netcoreapp3.0</TargetFramework>
???? <LangVersion>8.0</LangVersion>
?
Nullable reference types
空引用對于所有編程者來說相信都是一個非常頭痛的問題,圖靈獎得主Tony Hoare 就把包含空引用的編程語言用定義為一個十億美元的錯誤Null References: The Billion Dollar Mistake。
首先還是來一段簡單的代碼:
???? string?s?=?null;
???? Console.WriteLine($"The?first?letter?of?{s}?is?{s[0]}");
這段代碼編譯沒有問題,但運行的時候會拋空引用異常的。
在C# 8.0中,開啟了空引用異常檢測后,上述代碼在編譯器就會檢查出告警來。
????
????
并且它會結合上下文判斷,如果該值不會為null,則不會告警,非常智能。
????
細心的朋友可能會發現,雖然在下面使用的地方沒有告警,但是變量初始化的地方還是報告警了。如果我們的程序本身就是允許null值改怎么辦呢,放任告警不管也是不合適的做法。
針對這個問題,C#引入了一個新的聲明為可空對象的語法:
???? string??s?=?null;
也就是在類型后加一個?符號,表面該對象是一個可空對象。
由于這個行為和之前的C#版本是不一致的,因此默認是沒有開啟這個功能的,我們需要在csproj文件中打開這個設置:
???? <LangVersion>8.0</LangVersion>
???? <NullableReferenceTypes>true</NullableReferenceTypes>
不知道在后續的VS的版本中會不會直接再界面上添加這一設置。
最后總結一下,Nullable reference types主要干了兩件事:
- 可以通過對象聲明判斷該對象是否可能為空。
- 當可空對象使用在不可空的場景是,會報告警。
雖然之前有一些第三方插件也集成了類似的功能,如Resharper的Null Check,但把這個功能集成到了編譯器上后更加簡潔好用。
C#的空對象檢查在設計期間也有好幾種語法方案,目前這種方案既解決了問題,又對現有代碼保持完全兼容,還能對現有代碼潛在性問題能進行分析,是一種比較理想的方案的。如果以后能通過設置,將空引用的告警級別可以設置為錯誤就更好了。
?
Ranges and indices
范圍和索引是C#新引入的語法,它主要引入了兩個對象Range和Index。
Index
首先還是來看一個簡單的例子。
???? var numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
???? Index i1 = 3; // number 3 from beginning
???? Index i2 = ^2; // number 2 from end
???? Console.WriteLine($"{numbers[i1]}, {numbers[i2]}"); // "4, 6"
這個例子簡單的演示了一下Index的用法,Index本身還是類似于之前的int索引的,它也可以和int類型轉換。但Index在int索引的方式擴展了一下,支持從后往前訪問,也就是我們說的倒數位。
???? Index i2 = ^2; // number 2 from end
Range
基于Index組成起點和終點,可以組成了一個范圍Range,根據Range可以對數組進行切片。
???? Range?range?=?Range.Create(i1,?i2);
???? int[]?slice?=?numbers[range];????????//"4,?5"
".."運算符
為了快速表示一個Range,C#還映入了一個新的運算符".."如上面的代碼就可以簡寫為:
???? int[]?slice?=?numbers[i1..i2];????????//"4,?5"
".."語法不復雜,通過".."連接的開頭和結尾的索引,用來表示一個范圍。為了使用方便,".."運算符的開頭和結尾是可以省略的,常用的大致就有這幾種形式。
???? string?text?=?"hello?c#?8.0";
???? Console.WriteLine(text[..]); //"hello c# 8.0"
???? Console.WriteLine(text[^3..]);??????//"8.0"
???? Console.WriteLine(text[..5]);???????//"hello"
???? Console.WriteLine(text[6..]);???????//"c#?8.0"
通過".."運算符,我們描述切片時可以清晰很多,例如如下這個常見的求字符串子串的例子:
???? var?sub?=?text.Substring(text.Length?-?6,?6);
???? var?sub2?=?text[^6..];
.net 3.0的很多類都內置了對Range的切片操作,常見的有:
- 字符串用來子串
- Array用來劃獲取子數組
- span<T>用來切片
?
Asynchronous streams
異步流能一種拉的方式進行異步迭代,配合async編程可以以異步的方式把socket流像本地文件一樣解析,相信這是很多用c#寫socket程序的程序員所喜歡的一個特性。
一個簡單的示例如下:
???? static?async?IAsyncEnumerable<string>?GetNamesAsync()
???? {
????????await?Task.Delay(1000);
????????yield?return?"hello";
????????await?Task.Delay(1000);
????????yield?return?"world";
???? }
????
???? await?foreach?(var?name?in?GetNamesAsync())
???? {
????????Console.WriteLine(name);
???? }
我在Visual Studio 2019 preview中試用這個功能的時候,發現無法編譯通過。MS解釋說這個是VS和.net core代碼沒有完全匹配上所致,我們可以手動添加相關代碼以完成這一編譯過程。?


namespace System.Threading.Tasks {using System.Runtime.CompilerServices;using global::System.Threading.Tasks.Sources;internal struct ManualResetValueTaskSourceLogic<TResult>{private ManualResetValueTaskSourceCore<TResult> _core;public ManualResetValueTaskSourceLogic(IStrongBox<ManualResetValueTaskSourceLogic<TResult>> parent) : this() { }public short Version => _core.Version;public TResult GetResult(short token) => _core.GetResult(token);public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token);public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);public void Reset() => _core.Reset();public void SetResult(TResult result) => _core.SetResult(result);public void SetException(Exception error) => _core.SetException(error);} }namespace System.Runtime.CompilerServices {internal interface IStrongBox<T> { ref T Value { get; } } }
?
其他語法
本身C# 8.0是還有幾個其它語法的,如接口默認方法,高級模式匹配等。這些語法在目前的VS 2019 preview中還無法體驗。估計后續會慢慢放開的,到時候我再寫相關文章介紹它們。
相關文章:
https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/
?