C#6.0 新增功能

C# 6.0 版本包含許多可提升開發人員工做效率的功能。 這些功能的整體效果是讓你編寫的代碼更簡潔、更具可讀性。 該語法不像許多常見作法那樣繁瑣。 能夠更輕鬆地看出設計意圖。 好好了解這些功能能夠幫助你提升生產力,編寫更具可讀性的代碼。 你能夠更專一於功能,而不是語言的構造。html

本文的其他部分是對每一個功能的概述,並提供用於探索每一個功能的連接。 還能夠在教程部分的 C# 6 交互式探索中探索這些功能。web

01 只讀自動屬性
只讀自動屬性提供了更簡潔的語法來建立不可變類型。 你聲明僅具備 get 訪問器的自動屬性:
public string FirstName { get; }
public string LastName { get;  }

FirstName 和 LastName 屬性只能在構造函數的主體中設置;express

嘗試在另外一種普通方法中設置 LastName 會生成 CS0200 編譯錯誤:api

此功能實現用於建立不可變類型的真正語言支持且使用更簡潔和方便的自動屬性語法。數組

02 自動屬性初始化表達式
自動屬性初始值設定項可以讓你在屬性聲明中聲明自動屬性的初始值。
   public class Student
    {
        public string FirstName { get; } = "";
        public string LastName { get; private set; } = "傳寧";
    }

FirstName,LaseName 成員在聲明它的位置處被初始化。 這樣,就能更容易地僅執行一次初始化。 初始化是屬性聲明的一部分,可更輕鬆地將存儲分配。安全

03 Expression-bodied(正文表達式) 函數成員
你編寫的許多成員是能夠做爲單個表達式的單個語句。 改成編寫 expression-bodied 成員。這適用於方法和只讀屬性。 例如,重寫 ToString() 一般是理想之選:
public override string ToString() => $"{LastName}, {FirstName}";

也能夠將此語法用於只讀屬性:app

public string FullName => $"{FirstName} {LastName}";

將現有成員更改成 expression bodied 成員是二進制兼容的更改async

04 靜態導入 using static
using static 加強功能可用於導入單個類的靜態方法。 指定要使用的類:
using static System.Math;

Math 不包含任何實例方法。 還可使用 using static 爲具備靜態和實例方法的類導入類的靜態方法。 最有用的示例之一是 Stringide

using static System.String;

在 using static 語句中必須使用徹底限定的類名 System.String。 而不能使用 string 關鍵字。函數

從 static using 語句導入時,僅在使用擴展方法調用語法調用擴展方法時,擴展方法纔在範圍內。 做爲靜態方法調用時,擴展方法不在範圍內。 你在 LINQ 查詢中會常常看到這種狀況。 能夠經過導入 Enumerable 或 Queryable 來導入 LINQ 模式。

using static System.Linq.Enumerable;

一般使用擴展方法調用表達式調用擴展方法。 在使用靜態方法調用語法對其進行調用的罕見狀況下,添加類名稱能夠解決歧義。

static using 指令還能夠導入任何嵌套的類型。 能夠引用任何嵌套的類型,而無需限定。

看下面的一個具體事例:

舊語法:

using System;

namespace Demo002_NF46_CS60
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello world");
        }
    }
}

引入 using static 語法:

using static System.Console;

namespace Demo002_NF46_CS60
{
    class Program
    {
        static void Main(string[] args)
        {
           WriteLine("Hello world");
        }
    }
}

關於using static 的更具體的信息,請參考《using 靜態指令》

05 Null 條件運算符
Null 條件運算符使 null 檢查更輕鬆、更流暢。 將成員訪問 . 替換爲 ?.
var first = person?.FirstName;

在前面的示例中,若是 Person 對象是 null,則將變量 first 賦值爲 null。 不然,將 FirstName 屬性的值分配給該變量。 最重要的是?. 意味着當 person 變量爲 null 時,此行代碼不會生成 NullReferenceException。 它會短路並返回 null。 還能夠將 null 條件運算符用於數組或索引器訪問。 將索引表達式中的 [] 替換爲 ?[]

當 FirstName 爲 null 時,變量 firstName 爲 null,打印輸出時不報錯:

不管 person 的值是什麼,如下表達式均返回 string。 一般,將此構造與「null 合併」運算符一塊兒使用,以在其中一個屬性爲 null 時分配默認值。 表達式短路時,鍵入返回的 null值以匹配整個表達式。

first = person?.FirstName ?? "Unspecified";

還能夠將 ?. 用於有條件地調用方法。 具備 null 條件運算符的成員函數的最多見用法是用於安全地調用可能爲 null 的委託(或事件處理程序)。 經過使用 ?. 運算符調用該委託的 Invoke 方法來訪問成員。 能夠在委託模式一文中看到示例。

?. 運算符的規則確保運算符的左側僅計算一次。 它支持許多語法,包括使用事件處理程序的如下示例:

// preferred in C# 6:
this.SomethingHappened?.Invoke(this, eventArgs);

確保左側只計算一次,這使得你能夠在 ?. 的左側使用任何表達式(包括方法調用)。

06 字符串內插
使用 C# 6,新的字符串內插功能能夠在字符串中嵌入表達式。 使用 $ 做爲字符串的開頭,並使用 { 和 } 之間的表達式代替序號:
public string FullName => $"{FirstName} {LastName}";

本示例使用替表明達式的屬性。 可使用任何表達式。 例如,能夠在內插過程當中計算學生的成績平均值:

public string GetGradePointPercentage() => $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";

上一行代碼將 Grades.Average() 的值格式設置爲具備兩位小數的浮點數。

一般,可能須要使用特定區域性設置生成的字符串的格式。 請利用經過字符串內插生成的對象能夠隱式轉換爲 System.FormattableString 這一事實。 FormattableString 實例包含組合格式字符串,以及在將其轉換爲字符串以前評估表達式的結果。 在設置字符串的格式時,可使用 FormattableString.ToString(IFormatProvider) 方法指定區域性。 下面的示例使用德語 (de-DE) 區域性生成字符串。 (德語區域性默認使用「,」字符做爲小數分隔符,使用「.」字符做爲千位分隔符。)

FormattableString str = $"Average grade is {s.Grades.Average()}";
var gradeStr = str.ToString(new System.Globalization.CultureInfo("de-DE"));

要開始使用字符串內插,請參閱 字符串內插 一文和  C# 中字符串內插、 符合格式設置 教程。

07 異常篩選器
「異常篩選器」是肯定什麼時候應該應用給定的 catch 子句的子句。 若是用於異常篩選器的表達式計算結果爲 true,則 catch 子句將對異常執行正常處理。 若是表達式計算結果爲 false,則將跳過 catch 子句。 一種用途是檢查有關異常的信息,以肯定 catch 子句是否能夠處理該異常:
public static async Task<string> MakeRequest()
{
    WebRequestHandler webRequestHandler = new WebRequestHandler();
    webRequestHandler.AllowAutoRedirect = false;
    using (HttpClient client = new HttpClient(webRequestHandler))
    {
        var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/");
        try
        {
            var responseText = await stringTask;
            return responseText;
        }
        catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
        {
            return "Site Moved";
        }
    }
}

至關於

 catch (System.Net.Http.HttpRequestException e) 
 {
    if(e.Message.Contains("301")) // 若是判斷的邏輯較多,建議使用該方式。
    {
        return "Site Moved";
    }
 }
08 nameof 表達式
nameof 表達式的計算結果爲符號的名稱。 每當須要變量、屬性或成員字段的名稱時,這是讓工具正常運行的好辦法。 nameof 的其中一個最多見的用途是提供引發異常的符號的名稱:
if (IsNullOrWhiteSpace(lastName))
{
   throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
}

另外一個用途是用於實現 INotifyPropertyChanged 接口的基於 XAML 的應用程序:

private string lastName;
public string LastName
{
    get { return lastName; }
    set
    {
        if (value != lastName)
        {
            lastName = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LastName)));
        }
    }
}
09 Catch 和 Finally 塊中的 Await
C# 5 對於可放置 await 表達式的位置有若干限制。 使用 C# 6,如今能夠在 catch 或 finally 表達式中使用 await。 這一般用於日誌記錄方案:
public static async Task<string> MakeRequestAndLogFailures()
{ 
    await logMethodEntrance();
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try 
{
var responseText = await streamTask; return responseText; } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301")) { await logError("Recovered from redirect", e); return "Site Moved"; } finally { await logMethodExit(); client.Dispose(); } }

在 catch 和 finally 子句中添加 await 支持的實現細節可確保該行爲與同步代碼的行爲一致。 當在 catch 或 finally 子句中執行的代碼引起異常時,執行將在下一個外層塊中查找合適的 catch 子句。 若是存在當前異常,則該異常將丟失。 catch 和 finally 子句中的 awaited 表達式也會發生一樣的狀況:搜索合適的 catch,而且當前異常(若是有)將丟失。

鑑於此行爲,建議仔細編寫 catch 和 finally 子句,避免引入新的異常。

10 使用索引器初始化關聯集合
索引初始值設定項是提升集合初始值設定項與索引用途一致性的兩個功能之一。 在早期版本的 C# 中,能夠將集合初始值設定項用於序列樣式集合,包括在鍵值對周圍添加括號而獲得 Dictionary<TKey,TValue>
private Dictionary<int, string> messages = new Dictionary<int, string>
{
    { 404, "Page not Found"},
    { 302, "Page moved, but left a forwarding address."},
    { 500, "The web server can't come out to play today."}
};

能夠將集合初始值設定項與 Dictionary<TKey,TValue> 集合和其餘類型一塊兒使用,在這種狀況下,可訪問的 Add 方法接受多個參數。 新語法支持使用索引分配到集合中:

private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
    [404] = "Page not Found",
    [302] = "Page moved, but left a forwarding address.",
    [500] = "The web server can't come out to play today."
};

此功能意味着,可使用與多個版本中已有的序列容器語法相似的語法初始化關聯容器。

11 集合初始值設定項中的擴展 Add 方法
使集合初始化更容易的另外一個功能是對 Add 方法使用擴展方法。 添加此功能的目的是進行 Visual Basic 的奇偶校驗。 若是自定義集合類的方法具備經過語義方式添加新項的名稱,則此功能很是有用。
12 改進了重載解析
 在之前的一些構造中,之前版本的 C# 編譯器可能會發現涉及 lambda 表達式的一些方法不明確。 請考慮此方法:
static Task DoThings() 
{
     return Task.FromResult(0); 
}

在早期版本的 C# 中,使用方法組語法調用該方法將失敗:

Task.Run(DoThings);

早期的編譯器沒法正確區分 Task.Run(Action) 和 Task.Run(Func<Task>())。 在早期版本中,須要使用 lambda 表達式做爲參數:

Task.Run(() => DoThings());

C# 6 編譯器正確地肯定 Task.Run(Func<Task>()) 是更好的選擇。

肯定性的編譯器選項

-deterministic 選項指示編譯器爲同一源文件的後續編譯生成徹底相同的輸出程序集。

默認狀況下,每一個編譯都生成惟一的輸出內容。 編譯器添加一個時間戳和一個隨機生成的 GUID。 若是想按字節比較輸出以確保各項生成之間的一致性,請使用此選項。

有關詳細信息,請參閱 -deterministic 編譯器選項文章。

 

相關文章
相關標籤/搜索