1. 程式人生 > >[翻譯]初試C# 8.0

[翻譯]初試C# 8.0

原文地址: https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/

初試C# 8.0

昨天我們宣佈了Visual Studio 2019的第一個預覽版(使用Visual Studio 2019提高每個開發人員的工作效率)和.NET Core 3.0(宣佈.NET Core 3預覽1和開源Windows桌面框架)。

其中一個令人興奮的方面是你可以使用C#8.0中的一些功能!在這裡,我將帶您進行一次導遊,瞭解您可以在預覽中嘗試的三種新的C#功能。並非所有C#8.0功能都可用。如果您想了解所有主要功能,請閱讀最近釋出Building C# 8.0,或檢視Channel 9或YouTube。

做好準備

首先,下載並安裝.NET Core 3.0的預覽版1和Visual Studio的2019的預覽版1。在Visual Studio中,確保選擇工作負載“.NET Core跨平臺開發”(如果您忘記了,可以稍後通過開啟Visual Studio安裝程式並單擊Visual Studio 2019預覽頻道上的“修改”來新增它)。

啟動Visual Studio 2019預覽版,建立新專案,然後選擇“Console App(.NET Core)”作為專案型別。

專案啟動並執行後,將其目標框架更改為.NET Core 3.0(在解決方案資源管理器中右鍵單擊該專案,選擇“屬性”並使用“應用程式”選項卡上的下拉選單)。然後選擇C#8.0作為語言版本(在專案頁面的Build選項卡上單擊“Advanced ...”並選擇“C#8.0(beta)”)。

現在,您可以輕鬆獲得所有語言功能和支援框架型別!

可空的引用型別

可空引用型別功能旨在警告您程式碼中的null不安全行為。既然我們之前沒有這樣做過,那麼現在就開始改變吧!為避免這種情況,您需要選擇加入該功能。

不過,在我們開啟它之前,讓我們寫一些非常糟糕的程式碼:

640?wx_fmt=png

如果你執行它,你當然會得到一個空引用異常。你陷入了黑洞!你怎麼知道不要在特定的地方間接引用s?嗯,因為在前一行分配了null。但是在現實生活中,它可能不是在前一行,而是在你編寫程式碼的三年後在地球另一端執行的其他人程式集中。你怎麼知道不寫那個?這是可空引用型別要回答的問題!所以讓我們開啟它們吧!

對於一個新專案,你應該立即開啟它們。事實上,我認為它們應該在新專案中預設啟用,但我們在預覽中沒有這樣做。開啟它們的方法是將以下行新增到.csproj檔案中,例如在切換到上面的C#8.0時剛剛插入的LanguageVersion之後:

<NullableReferenceTypes>true</NullableReferenceTypes>

儲存.csproj檔案並返回到您的程式:發生了什麼?你有兩個警告!每個代表一個功能的“一半”。讓我們依次看看它們。第一個是null這一行:

string s = null;

它抱怨你將null賦給“不可空型別”:啥?!?當開啟該功能時,在普通的引用型別中不再歡迎使用null,例如string!因為,你知道嗎,null不是一個字串!我們一直假裝在過去的五十年在面向物件程式設計,但實際上null並不是一個物件:這就是為什麼每當你試圖將它做為物件時一切都會爆炸!

所以不多說:null是禁止的,除非你要求它。你是怎麼要求的?通過使用可空的引用型別,例如string?。尾隨問號表示允許null:

string? s = null;

警告消失了:我們已明確表達了此變數保持null的意圖,所以現在沒問題了。

直到下一行程式碼!在該行:

WriteLine($"The first letter of {s} is {s[0]}");

它抱怨s中s[0],你可能會間接引用一空引用。果然:是!幹得好,編譯器!你怎麼解決它?嗯,這幾乎取決於你 - 無論何種方式你得修復它!讓我們嘗試初學者的方法, 只在s非null時執行該行:

if (s != null) WriteLine($"The first letter of {s} is {s[0]}");

警告消失了!為什麼?因為編譯器可以看到,只有s不是null時才會走後面的程式碼。它實際上進行了全流分析,跟蹤每行程式碼中的每個變數,以便密切關注它可能是null的和可能不是的位置。它會監視您的測試和作業,並進行簿記(bookkeeping)。

我們試試另一種方法:

WriteLine($"The first letter of {s} is {s?[0] ?? '?'}");

這使用null條件索引運算子s?[0],它避免了間接引用,如果s為null ,則生成null。現在我們有一個可空的char?,但是null合併運算子?? '?'替換null值為字元 '?'。因此避免了所有null間接引用。編譯器很高興,沒有給出警告。

正如您所看到的,該功能可以讓您在編寫程式碼時保持誠實:它會強制您在系統中使用null時通過使用可空的引用型別來表達您的意圖。並且一旦出現null,它就會強制您負責任地處理它,讓您在存在可能間接引用null值以觸發空引用異常的風險時進行檢查。

你現在完全null安全了嗎?沒有。有幾種方法可以使null值漏掉並導致空引用異常:

  • 如果你呼叫沒有可空的引用型別功能的程式碼(也許它是在該功能存在之前編譯的),那麼我們無法知道該程式碼的意圖是什麼:它沒有區分可空和不可空 - 我們說它是“無視的”。所以我們給它一個通行證; 我們根本不會對此類呼叫發出警告。

  • 分析器本身有一些漏洞。其中大多數是安全和便利之間的權衡; 如果我們抱怨,那將很難修復。例如,當你編寫時new string[10],我們建立一個充滿null值的陣列,型別為非null字串。我們不會對此發出警告,因為編譯器如何跟蹤您初始化所有陣列元素?

但總的來說,如果你廣泛使用這個功能(即在任何地方開啟它),它應該照顧絕大多數的空引用。

毫無疑問地,我們打算在現有程式碼上開始使用該功能!一旦開啟它,您可能會收到很多警告。其中一些實際上代表了問題:是的,你發現了一個錯誤!其中一些可能有點煩人; 你的程式碼顯然是null安全的,你只是沒有工具來表達你的意圖:你沒有可空的引用型別!例如,在我們開始的行:

string s = null;

這在現有程式碼中將非常普遍!正如你所看到的那樣,我們也確實在下一行發出了警告,我們試圖間接引用它。因此,從安全的角度來看,此處的賦值警告嚴格來說是多餘的:它使您在新程式碼中保持誠實,但修復現有程式碼中的所有事件並不會使其更安全。對於這種情況,我們正在處理一種模式,其中某些警告被關閉,當它不影響空安全性時,因此升級現有程式碼不那麼令人生畏。

另一個有助於升級的功能是,您可以使用編譯器指令#nullable enable#nullable disable在程式碼中“本地”開啟或關閉該功能。這樣你就可以逐步完成你的專案並逐步處理註釋和警告。

要了解更多關於可空引用型別檢查出Overview of Nullable types和Introduction to nullable tutorial。

為了更深入的設計理由,去年我在C#中寫了一篇帖子Introducing Nullable Reference Types in C#。

如果您想讓自己沉浸在設計工作的日常工作中,請檢視GitHub上的Language Design Notes,或者Nullable Reference Types Specification。

範圍和索引

使用索引資料結構時,C#的表現力越來越強。曾經想要簡單的語法來切出陣列,字串或span的一部分嗎?現在你可以!

繼續將您的程式更改為以下內容:

640?wx_fmt=png

讓我們來看看迭代名字陣列的那段程式碼。修改foreach如下:

foreach (var name in names[1..4])

看起來我們正在迭代名字1到4。事實上程式碼執行時也確實如此!終點是排外的,即不包括元素4。1..4實際上是一個範圍表示式,它不必像該處一樣,作為索引操作的一部分出現。它有一種自己的型別,叫做Range。如果我們想要的話,我們可以把它拉到自己的變數中,它會起到同樣的作用:

Range range = 1..4; 
foreach (var name in names[range])

範圍表示式的終點不必是整數。事實上,它們屬於一種型別,叫Index,可由非負數轉換得來。但是你也可以使用一個新的^運算子建立Index,意思是“從末尾”。所以^1是從末尾開始1個:

foreach (var name in names[1..^1])

這會在陣列的每一端去除一個元素,產生一個帶有中間三個元素的陣列。

範圍表示式可以在任一端或兩端開啟。..^10..^1相同。1..1..^0相同。並且..0..^0相同:從頭到尾。試試吧!嘗試在Range的兩端混合使用“從開始”和“從末尾”的Index,看看會發生什麼。

範圍不僅僅適用於索引器。例如,我們計劃有過載string.SubStringSPan<T>.Slice以及使用Range引數的AsSpan擴充套件方法。這些不在.NET Core 3.0預覽中。

非同步流

IEnumerable<T>在C#中扮演著特殊的角色。“IEnumerables”代表各種不同的資料序列,並且語言具有用於消費和生成它們的特殊構造。

正如我們在當前的程式中看到的那樣,它們通過foreach宣告來消費,該宣告涉及獲取列舉器的苦差事,反覆推進它,沿途提取元素,最後處理列舉器。並且可以使用迭代器生成它們:yield return按消費者要求產生元素。但兩者都是同步的:當結果被請求時最好已經準備就緒,否則就會阻塞執行緒!

async和await加入到C#中用來處理當結果被請求時不一定準備好的情況。它們可以非同步await,並且執行緒可以在其可用之前執行其他操作。但這僅適用於單個值,而不適用於隨時間逐漸和非同步生成的序列,例如來自IoT感測器的測量值或來自服務的流資料。

非同步流在C#中將非同步和列舉結合在一起!讓我們看看,通過逐步“非同步”我們當前的程式。

首先,讓我們在檔案的頂部新增另一個using指令:

using System.Threading.Tasks;

現在讓我們通過在yield return名字之前增加一個非同步延遲來模擬GetNames做了一些非同步工作:

await Task.Delay(1000);yield return name;

當然,我們得到了一個錯誤: 只能在async方法中使用await。所以我們讓它非同步:

static async IEnumerable<string> GetNames()

現在我們被告知我們沒有為非同步方法返回正確的型別,這是公平的。但除了通常的Task東西之外,這次我們的型別列表中有了一個新的候選可以返回:IAsyncEnumerable

static async IAsyncEnumerable<string> GetNames()

就像我們已經生成了一個非同步字串流!根據命名指南,讓我們重新命名GetNames為GetNamesAsync。

static async IAsyncEnumerable<string> GetNamesAsync()

現在我們在Main方法中的這一行得到一個錯誤:

foreach (var name in GetNamesAsync())

不知道如何foreach一個IAsyncEnumerable<T>。這是因為非同步流的foreach需要顯式使用await關鍵字:

await foreach (var name in GetNamesAsync())

這是foreach的非同步版本:採用非同步流並等待每個元素!當然它只能在非同步方法中這樣做,所以我們必須使我們的Main方法非同步。幸運的是,C#7.2增加了對它的支援:

static async Task Main(string[] args)

現在所有的混亂都消失了,程式是正確的。但是如果你嘗試編譯並執行它,你會得到一些令人尷尬的錯誤。那是因為我們搞砸了一下,並沒有完全對齊.NET Core 3.0和Visual Studio 2019的預覽。具體來說,有一種實現型別,非同步迭代器利用它與編譯器期望的不同。

您可以通過向專案新增單獨的原始檔來修復此問題,其中包含此橋接程式碼。再次編譯,一切都應該工作得很好。

下一步

請讓我們知道你的想法!如果您嘗試這些功能並瞭解如何改進它們,請使用Visual Studio 2019預覽中的反饋按鈕。預覽的整個目的是根據現實使用者手中的功能如何進行最後一次校正,所以請告訴我們!

原文地址:https://www.cnblogs.com/waku/p/10094691.html



  

.NET社群新聞,深度好文,歡迎訪問公眾號文章彙總 http://www.csharpkit.com

640?wx_fmt=jpeg