C# 7.0 的新特性(預覽版)

來源:互聯網
上載者:User
《〔譯〕 C# 7 的新特性》花了很大的篇幅來介紹 C# 7.0 的 9 個新特性,這裡我根據項目經驗,通過執行個體對它們進行一個快速的介紹,讓大家能在短時間內瞭解它們。

總的來說,這些新特性使 C# 7.0 更容易以函數式編程的思想來寫代碼,C# 6.0 在這條路上已經做了不少工作, C# 7.0 更近一步!

運算式 everywhere

C# 6.0 中,可以對成員方法和唯讀屬性使用 Lambda 運算式,當時最鬱悶的就是為什麼不支援屬性的 set 訪問器。現在好了,不僅 set 方法器支援使用 Lambda 運算式,構造方法、析構方法以及索引都支援以 Lambda 運算式方式定義了。

class SomeModel{    private string internalValue;    public string Value    {        get => internalValue;        set => internalValue = string.IsNullOrWhiteSpace(value) ? null : value;    }}

out 變數

out 變數是之前就存在的文法,C# 7.0 只是允許它將申明和使用放在一起,避免多一行代碼。最直接的效果,就是可以將兩個語句用一個運算式完成。這裡以一個簡化版的 Key 類為例,這個類早期被我們用於處理通過 HTTP Get/Post 傳入的 ID 值。

public class Key{    public string Value { get; }    public Key(string key)    {        Value = key;    }    public int IntValue    {        get        {            // C# 6.0,需要提前定義 intValue,但不需要初始化            // 雖然 C# 6.0 可以為唯讀屬性使用 Lambda 運算式            // 但這裡無法用一個運算式表達出來            int intValue;            return int.TryParse(Value, out intValue) ? intValue : 0;        }    }}

而在 C# 7 中就簡單了

// 注意 out var intValue,// 對於可推導的類型甚至可以用 var 來申明變數public int IntValue => int.TryParse(Value, out var intValue) ? intValue : 0;

元組和解構

用過 System.Tuple 的朋友一定對其 Item1Item2 這樣毫無語義的命名深感不爽。不過 C# 7.0 帶來了語義化的命名,同時,還減化了元組的建立,不再需要 Tuple.Create(...)。另外,要使用新的元組特性和解構,需要引入 NuGet 包 System.ValueTuple

Install-Package System.ValueTuple

當然,元組常用於返回多個值的方法。也有些人喜歡用 out 參數來返回,但即使現在可以 out 變數,我仍然不贊成廣泛使用 out 參數。

下面這個樣本方法用於返回一個預設的時間範圍(從今天開始算往前一共 7 天),用於資料檢索。

// 傳回型別是一個包含兩個元素的元組(DateTime Begin, DateTime End) GetDefaultDateRange(){    var end = DateTime.Today.AddDays(1);    var begin = end.AddDays(-7);    // 這裡使用一對圓括弧就建立了一個元組    return (begin, end);}

調用這個方法可以獲得元組,因為定義的時候傳回值指定了每個資料成員的名稱,所以從元組擷取資料可以是語義化的,當然仍然可以使用 Item1Item2

var range = GetDefaultDateRange();var begin = range.Begin;    // 也可以 begin = range.Item1var end = range.End;        // 也可以 end = range.Item2

上面這個例子還可以簡化,不用 range 這個中間變數,這就用到瞭解構

var (begin, end) = GetDefaultDateRange();

這裡建立元組是以傳回值來舉例的,其實它就是一個運算式,可以在任何地方建立元組。上面的例子邏輯很簡單,可以用運算式解決。下面的樣本順便示範了非語義化的傳回型別申明。

// 原來的 (DateTime Begin, DateTime End) 申明也是沒問題的(DateTime, DateTime) GetDefaultDateRange()    => (DateTime.Today.AddDays(1).AddDays(-7), DateTime.Today.AddDays(1));

解構方法 Deconstrct

解構方法可以讓任何類(而不僅僅是元組)按定義的參數進行解構。而且神奇的是解構方法可以是成員方法,也可以定義成擴充方法。

public class Size{    public int Width { get; }    public int Height { get; }    public int Tall { get; }    public Size(int width, int height, int tall)    {        this.Width = width;        this.Height = height;        this.Tall = tall;    }    // 定義成成員方法的解構    public void Deconstruct(out int width, out int height)    {        width = Width;        height = Height;    }}public static class SizeExt{    // 定義成擴充方法的解構    public static void Deconstruct(this Size size, out int width, out int height, out int tall)    {        width = size.Width;        height = size.Height;        tall = size.Tall;    }}

下面是使用解構的代碼

var size = new Size(1920, 1080, 10);var (w, h) = size;var (x, y, z) = size;

改造 Size 的構造方法

還記得前面提到的構造方法可以定義為 Lambda 運算式嗎?下面是使用元組和 Lambda 對 Size 構造方法的改造——我已經醉了!

public Size(int width, int height, int tall)    => (Width, Height, Tall) = (width, height, tall);

模式比對

模式比對目前支援 isswitch。說起來挺高大上的一個名字,換個接地氣一點的說法就是判斷類型順便定義個具體類型的引用,有興趣還可以加再點額外的判斷。

對於 is 來說,就是判斷的時候順便定義個變數再初始化一下,所以像原來這樣寫的代碼

// 假設邏輯能保證這裡的 v 可能是 string 也 可能是 intstring ToString(object v) {    if (v is int) {        int n = (int) v;        return n.ToString("X4");    } else {        return (string) n;    }}

可以簡化成——好吧,直接一步到位寫成運算式好了

string ToString(object v)    => (v is int n) ? n.ToString("X4") : (string) v;

當然你可能說之前的那個也可以簡化成一個運算式——好吧,不深究這個問題好嗎?我只是示範 is 的模式比對而已。

switch 中的模式比對似乎要有用得多,還是以 ToString 為例吧

static string ToString(object v){    switch (v)    {        case int n when n > 0xffff:            // 判斷類型,匹配的情況下再對值進行一個判斷            return n.ToString("X8");        case int n:            // 判斷類型,這裡 n 肯定 <= 0xffff            return n.ToString("X4");        case bool b:            return b ? "ON" : "OFF";        case null:            return null;        default:            return v.ToString();    }}

注意一下上面第一個分支中 when 的用法就好了。

ref 局部變數和 ref 傳回值

這已經是很接近 C/C++ 的一種用法了。雖然官方說法是這樣做可以解決一些安全性問題,但我個人目前還是沒遇到它的使用情境。如果設計足夠好,在目前又加入了元組新特性和解構的情況下,個人認為幾乎可以避免使用 outref

既然沒用到,我也不多說了,有用到的同學來討論一下!

數字字面量文法增強

這裡有兩點增強,一點是引入了 0b 首碼的位元字面量文法,另一點是可以在數值字面量中任意使用 _ 對數字進行分組。這個不用多數,舉兩個例就明白了

const int MARK_THREE = 0b11;            // 0x03const int LONG_MARK = 0b_1111_1111;     // 0xffconst double PI = 3.14_1592_6536

局部函數

經常寫 JavaScript 的同學肯定會深有體會,局部函數是個好東西。當然它在 C# 中帶來的最大好處是將某些程式碼群組織在了一起。我之前在項目中大量使用了 Lambda 來代替局部函數,現在可以直接替換成局部函數了。Labmda 和局部函數雖然多數情況下能做同樣的事情,但是它們仍然有一些區別

  • 對於 Lambda,編譯器要乾的事情比較多。總之呢,就是編譯效率要低得多

  • Lambda 通過委託實現,調用過程比較複雜,局部函數可以直接調用。簡單地說就是局部函數執行效率更高

  • Lambda 必須先定義再使用,局部函數可以定義在使用之後。據說這在對遞迴演算法的支援上會有區別

比較常用的地方是 Enumerator 函數和 async 函數中,因為它們實際都不是立即執行的。

我在項目中多是用來組織代碼。局部函數代替只被某一個公用 API 呼叫的私人函數來組織代碼雖然不失為一個簡化類結構的好方法,但是把公用 API 函數的函數體拉長。所以很多時候我也會使用內部類來代替某些私人函數來組織代碼。這裡順便說一句,我不贊成使用 #region 組織代碼。

支援更多 async 傳回型別

如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比較像 Promise 的角色。不用羨慕 JavaScript 的 async 支援 Promise like,現在 C# 的 async 也支援 Task like 了,只要實現了 GetAwaiter 方法就行。

官方提供了一個 ValueTask 作為樣本,可以通過 NuGet 引入:

Install-Package System.Threading.Tasks.Extensions

這個 ValueTask 比較有用的一點就是相容了資料類型和 Task:

string cache;ValueTask<string> GetData(){    return cache == null ? new ValueTask<string>(cache) : new ValueTask<string>(GetRemoteData());    // 局部函數    async Task<string> GetRemoteData()    {        await Task.Delay(100);        return "hello async";    }}


《〔譯〕 C# 7 的新特性》花了很大的篇幅來介紹 C# 7.0 的 9 個新特性,這裡我根據項目經驗,通過執行個體對它們進行一個快速的介紹,讓大家能在短時間內瞭解它們。

總的來說,這些新特性使 C# 7.0 更容易以函數式編程的思想來寫代碼,C# 6.0 在這條路上已經做了不少工作, C# 7.0 更近一步!

運算式 everywhere

C# 6.0 中,可以對成員方法和唯讀屬性使用 Lambda 運算式,當時最鬱悶的就是為什麼不支援屬性的 set 訪問器。現在好了,不僅 set 方法器支援使用 Lambda 運算式,構造方法、析構方法以及索引都支援以 Lambda 運算式方式定義了。

class SomeModel{    private string internalValue;    public string Value    {        get => internalValue;        set => internalValue = string.IsNullOrWhiteSpace(value) ? null : value;    }}

out 變數

out 變數是之前就存在的文法,C# 7.0 只是允許它將申明和使用放在一起,避免多一行代碼。最直接的效果,就是可以將兩個語句用一個運算式完成。這裡以一個簡化版的 Key 類為例,這個類早期被我們用於處理通過 HTTP Get/Post 傳入的 ID 值。

public class Key{    public string Value { get; }    public Key(string key)    {        Value = key;    }    public int IntValue    {        get        {            // C# 6.0,需要提前定義 intValue,但不需要初始化            // 雖然 C# 6.0 可以為唯讀屬性使用 Lambda 運算式            // 但這裡無法用一個運算式表達出來            int intValue;            return int.TryParse(Value, out intValue) ? intValue : 0;        }    }}

而在 C# 7 中就簡單了

// 注意 out var intValue,// 對於可推導的類型甚至可以用 var 來申明變數public int IntValue => int.TryParse(Value, out var intValue) ? intValue : 0;

元組和解構

用過 System.Tuple 的朋友一定對其 Item1Item2 這樣毫無語義的命名深感不爽。不過 C# 7.0 帶來了語義化的命名,同時,還減化了元組的建立,不再需要 Tuple.Create(...)。另外,要使用新的元組特性和解構,需要引入 NuGet 包 System.ValueTuple

Install-Package System.ValueTuple

當然,元組常用於返回多個值的方法。也有些人喜歡用 out 參數來返回,但即使現在可以 out 變數,我仍然不贊成廣泛使用 out 參數。

下面這個樣本方法用於返回一個預設的時間範圍(從今天開始算往前一共 7 天),用於資料檢索。

// 傳回型別是一個包含兩個元素的元組(DateTime Begin, DateTime End) GetDefaultDateRange(){    var end = DateTime.Today.AddDays(1);    var begin = end.AddDays(-7);    // 這裡使用一對圓括弧就建立了一個元組    return (begin, end);}

調用這個方法可以獲得元組,因為定義的時候傳回值指定了每個資料成員的名稱,所以從元組擷取資料可以是語義化的,當然仍然可以使用 Item1Item2

var range = GetDefaultDateRange();var begin = range.Begin;    // 也可以 begin = range.Item1var end = range.End;        // 也可以 end = range.Item2

上面這個例子還可以簡化,不用 range 這個中間變數,這就用到瞭解構

var (begin, end) = GetDefaultDateRange();

這裡建立元組是以傳回值來舉例的,其實它就是一個運算式,可以在任何地方建立元組。上面的例子邏輯很簡單,可以用運算式解決。下面的樣本順便示範了非語義化的傳回型別申明。

// 原來的 (DateTime Begin, DateTime End) 申明也是沒問題的(DateTime, DateTime) GetDefaultDateRange()    => (DateTime.Today.AddDays(1).AddDays(-7), DateTime.Today.AddDays(1));

解構方法 Deconstrct

解構方法可以讓任何類(而不僅僅是元組)按定義的參數進行解構。而且神奇的是解構方法可以是成員方法,也可以定義成擴充方法。

public class Size{    public int Width { get; }    public int Height { get; }    public int Tall { get; }    public Size(int width, int height, int tall)    {        this.Width = width;        this.Height = height;        this.Tall = tall;    }    // 定義成成員方法的解構    public void Deconstruct(out int width, out int height)    {        width = Width;        height = Height;    }}public static class SizeExt{    // 定義成擴充方法的解構    public static void Deconstruct(this Size size, out int width, out int height, out int tall)    {        width = size.Width;        height = size.Height;        tall = size.Tall;    }}

下面是使用解構的代碼

var size = new Size(1920, 1080, 10);var (w, h) = size;var (x, y, z) = size;

改造 Size 的構造方法

還記得前面提到的構造方法可以定義為 Lambda 運算式嗎?下面是使用元組和 Lambda 對 Size 構造方法的改造——我已經醉了!

public Size(int width, int height, int tall)    => (Width, Height, Tall) = (width, height, tall);

模式比對

模式比對目前支援 isswitch。說起來挺高大上的一個名字,換個接地氣一點的說法就是判斷類型順便定義個具體類型的引用,有興趣還可以加再點額外的判斷。

對於 is 來說,就是判斷的時候順便定義個變數再初始化一下,所以像原來這樣寫的代碼

// 假設邏輯能保證這裡的 v 可能是 string 也 可能是 intstring ToString(object v) {    if (v is int) {        int n = (int) v;        return n.ToString("X4");    } else {        return (string) n;    }}

可以簡化成——好吧,直接一步到位寫成運算式好了

string ToString(object v)    => (v is int n) ? n.ToString("X4") : (string) v;

當然你可能說之前的那個也可以簡化成一個運算式——好吧,不深究這個問題好嗎?我只是示範 is 的模式比對而已。

switch 中的模式比對似乎要有用得多,還是以 ToString 為例吧

static string ToString(object v){    switch (v)    {        case int n when n > 0xffff:            // 判斷類型,匹配的情況下再對值進行一個判斷            return n.ToString("X8");        case int n:            // 判斷類型,這裡 n 肯定 <= 0xffff            return n.ToString("X4");        case bool b:            return b ? "ON" : "OFF";        case null:            return null;        default:            return v.ToString();    }}

注意一下上面第一個分支中 when 的用法就好了。

ref 局部變數和 ref 傳回值

這已經是很接近 C/C++ 的一種用法了。雖然官方說法是這樣做可以解決一些安全性問題,但我個人目前還是沒遇到它的使用情境。如果設計足夠好,在目前又加入了元組新特性和解構的情況下,個人認為幾乎可以避免使用 outref

既然沒用到,我也不多說了,有用到的同學來討論一下!

數字字面量文法增強

這裡有兩點增強,一點是引入了 0b 首碼的位元字面量文法,另一點是可以在數值字面量中任意使用 _ 對數字進行分組。這個不用多數,舉兩個例就明白了

const int MARK_THREE = 0b11;            // 0x03const int LONG_MARK = 0b_1111_1111;     // 0xffconst double PI = 3.14_1592_6536

局部函數

經常寫 JavaScript 的同學肯定會深有體會,局部函數是個好東西。當然它在 C# 中帶來的最大好處是將某些程式碼群組織在了一起。我之前在項目中大量使用了 Lambda 來代替局部函數,現在可以直接替換成局部函數了。Labmda 和局部函數雖然多數情況下能做同樣的事情,但是它們仍然有一些區別

  • 對於 Lambda,編譯器要乾的事情比較多。總之呢,就是編譯效率要低得多

  • Lambda 通過委託實現,調用過程比較複雜,局部函數可以直接調用。簡單地說就是局部函數執行效率更高

  • Lambda 必須先定義再使用,局部函數可以定義在使用之後。據說這在對遞迴演算法的支援上會有區別

比較常用的地方是 Enumerator 函數和 async 函數中,因為它們實際都不是立即執行的。

我在項目中多是用來組織代碼。局部函數代替只被某一個公用 API 呼叫的私人函數來組織代碼雖然不失為一個簡化類結構的好方法,但是把公用 API 函數的函數體拉長。所以很多時候我也會使用內部類來代替某些私人函數來組織代碼。這裡順便說一句,我不贊成使用 #region 組織代碼。

支援更多 async 傳回型別

如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比較像 Promise 的角色。不用羨慕 JavaScript 的 async 支援 Promise like,現在 C# 的 async 也支援 Task like 了,只要實現了 GetAwaiter 方法就行。

官方提供了一個 ValueTask 作為樣本,可以通過 NuGet 引入:

Install-Package System.Threading.Tasks.Extensions

這個 ValueTask 比較有用的一點就是相容了資料類型和 Task:

string cache;ValueTask<string> GetData(){    return cache == null ? new ValueTask<string>(cache) : new ValueTask<string>(GetRemoteData());    // 局部函數    async Task<string> GetRemoteData()    {        await Task.Delay(100);        return "hello async";    }}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.