C# 6.0 版本包含許多可提高開發人員工作效率的功能。 這些功能的總體效果是讓你編寫的代碼更簡潔、更具可讀性。 該語法不像許多常見做法那樣繁瑣。 可以更輕松地看出設計意圖。 好好了解這些功能可以幫助你提高生產力,編寫更具可讀性的代碼。 你可以更專注於功能,而不是語言的構造。
本文的其余部分是對每個功能的概述,並提供用於探索每個功能的鏈接。 還可以在教程部分的 C# 6 交互式探索中探索這些功能。
public string FirstName { get; } public string LastName { get; }
FirstName 和 LastName 屬性只能在構造函數的主體中設置;
嘗試在另一種普通方法中設置 LastName 會生成 CS0200 編譯錯誤:

此功能實現用於創建不可變類型的真正語言支持且使用更簡潔和方便的自動屬性語法。
public class Student { public string FirstName { get; } = "張"; public string LastName { get; private set; } = "傳寧"; }
FirstName,LaseName 成員在聲明它的位置處被初始化。 這樣,就能更容易地僅執行一次初始化。 初始化是屬性聲明的一部分,可更輕松地將存儲分配。
ToString() 通常是理想之選:
public override string ToString() => $"{LastName}, {FirstName}";
也可以將此語法用於只讀屬性:
public string FullName => $"{FirstName} {LastName}";
將現有成員更改為 expression bodied 成員是二進制兼容的更改。
using static System.Math;
Math 不包含任何實例方法。 還可以使用 using static 為具有靜態和實例方法的類導入類的靜態方法。 最有用的示例之一是 String:
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 靜態指令》
. 替換為 ?.:
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);
確保左側只計算一次,這使得你可以在 ?. 的左側使用任何表達式(包括方法調用)。
$ 作為字符串的開頭,並使用 { 和 } 之間的表達式代替序號:
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"));
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"; } }
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))); } } }
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 子句,避免引入新的異常。
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." };
此功能意味着,可以使用與多個版本中已有的序列容器語法類似的語法初始化關聯容器。
Add 方法使用擴展方法。 添加此功能的目的是進行 Visual Basic 的奇偶校驗。 如果自定義集合類的方法具有通過語義方式添加新項的名稱,則此功能非常有用。
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 編譯器選項文章。
