准備工作:
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/
