[譯]初試C# 8.0

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

初試C# 8.0

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

其中一個使人興奮的方面是你可使用C#8.0中的一些功能!在這裏,我將帶您進行一次導遊,瞭解您能夠在預覽中嘗試的三種新的C#功能。並不是全部C#8.0功能均可用。若是您想了解全部主要功能,請閱讀最近發佈Building C# 8.0,或查看Channel 9YouTube編程

作好準備

首先,下載並安裝.NET Core 3.0的預覽版1Visual Studio的2019的預覽版1。在Visual Studio中,確保選擇工做負載「.NET Core跨平臺開發」(若是您忘記了,能夠稍後經過打開Visual Studio安裝程序並單擊Visual Studio 2019預覽頻道上的「修改」來添加它)。windows

啓動Visual Studio 2019預覽版,建立新項目,而後選擇「Console App(.NET Core)」做爲項目類型。數組

項目啓動並運行後,將其目標框架更改成.NET Core 3.0(在解決方案資源管理器中右鍵單擊該項目,選擇「屬性」並使用「應用程序」選項卡上的下拉菜單)。而後選擇C#8.0做爲語言版本(在項目頁面的Build選項卡上單擊「Advanced ...」並選擇「C#8.0(beta)」)。安全

如今,您能夠輕鬆得到全部語言功能和支持框架類型!數據結構

可空的引用類型

可空引用類型功能旨在警告您代碼中的null不安全行爲。既然咱們以前沒有這樣作過,那麼如今就開始改變吧!爲避免這種狀況,您須要選擇加入該功能。框架

不過,在咱們開啓它以前,讓咱們寫一些很是糟糕的代碼:異步

using static System.Console;

class Program
{
    static void Main(string[] args)
    {
        string s = null;
        WriteLine($"The first letter of {s} is {s[0]}");
    }
}

若是你運行它,你固然會獲得一個空引用異常。你陷入了黑洞!你怎麼知道不要在特定的地方間接引用s?嗯,由於在前一行分配了null。可是在現實生活中,它可能不是在前一行,而是在你編寫代碼的三年後在地球另外一端運行的其餘人程序集中。你怎麼知道不寫那個?這是可空引用類型要回答的問題!因此讓咱們打開它們吧!async

對於一個新項目,你應該當即打開它們。事實上,我認爲它們應該在新項目中默認啓用,但咱們在預覽中沒有這樣作。打開它們的方法是將如下行添加到.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 typesIntroduction to nullable tutorial

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

若是您想讓本身沉浸在設計工做的平常工做中,請查看GitHub上的Language Design Notes,或者Nullable Reference Types Specification

範圍和索引

使用索引數據結構時,C#的表現力愈來愈強。曾經想要簡單的語法來切出數組,字符串或span的一部分嗎?如今你能夠!

繼續將您的程序更改成如下內容:

using System.Collections.Generic;
using static System.Console;

class Program
{
    static void Main(string[] args)
    {
        foreach (var name in GetNames())
        {
            WriteLine(name);
        }
    }

    static IEnumerable<string> GetNames()
    {
        string[] names =
        {
            "Archimedes", "Pythagoras", "Euclid", "Socrates", "Plato"
        };
        foreach (var name in names)
        {
            yield return name;
        }
    }
}

讓咱們來看看迭代名字數組的那段代碼。修改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 。這是咱們的異步版IEnumerable !讓咱們返回它:

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預覽中的反饋按鈕。預覽的整個目的是根據現實用戶手中的功能如何進行最後一次校訂,因此請告訴咱們!

編碼快樂,

Mads Torgersen,C#設計負責人

相關文章
相關標籤/搜索