首頁 > 後端開發 > C#.Net教程 > C# 7.0 的新特性(速覽版)

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

巴扎黑
發布: 2017-04-15 09:05:55
原創
2065 人瀏覽過

《〔譯〕 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);
}
登入後複製
登入後複製

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

var range = GetDefaultDateRange();
var begin = range.Begin;    // 也可以 begin = range.Item1
var 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 也 可能是 int
string 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;            // 0x03
const int LONG_MARK = 0b_1111_1111;     // 0xff
const 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> 就比较像 <code>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);
}
登入後複製
登入後複製

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

var range = GetDefaultDateRange();
var begin = range.Begin;    // 也可以 begin = range.Item1
var 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 也 可能是 int
string 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;            // 0x03
const int LONG_MARK = 0b_1111_1111;     // 0xff
const 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> 就比较像 <code>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.0 的新特性(速覽版)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板