Share via


C# 6 新功能概觀

C# 語言第 6 版會持續發展語言,讓程式代碼的重複使用程式代碼較少、更清楚且更一致性。 更清楚的初始化語法、在 catch/finally 區塊中使用 await 的能力,以及 null 條件式 ? 運算子特別有用。

注意

如需最新版 C# 語言 - 第 7 版的資訊,請參閱 C# 7.0 的新功能一文

本文件介紹 C# 6 的新功能。 Mono 編譯程式完全支援它,開發人員可以開始在所有 Xamarin 目標平臺上使用新功能。

C# 6 影片的新功能

使用 C# 6

C# 6 編譯程式會用於所有最新版的 Visual Studio for Mac。 使用命令行編譯程式的人應該確認傳 mcs --version 回 4.0 或更高版本。 Visual Studio for Mac 使用者可以參考 關於 Visual Studio for Mac Visual Studio for Mac >> 顯示詳細數據,檢查他們是否已安裝 Mono 4(或更新版本)。

較少未定案

使用靜態

列舉和某些類別,例如 System.Math,主要是靜態值和函式的持有者。 在 C# 6 中,您可以使用單 using static 一語句匯入類型的所有靜態成員。 比較 C# 5 和 C# 6 中的一般三角函數:

// Classic C#
class MyClass
{
    public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
    {
        var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
        return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
    }
}

// C# 6
using static System.Math;

class MyClass
{
    public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
    {
        var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
        return Tuple.Create (Asin (tmp), Acos (tmp));
    }
}

using static 不會讓公用 const 欄位,例如 Math.PIMath.E直接存取:

for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ... 
//PI is const, not static, so requires Math.PI

搭配擴充方法使用靜態

using static 設施的運作方式與擴充方法稍有不同。 雖然擴充方法是使用 static撰寫的,但是若沒有要操作的實例,它們就沒有意義。 因此,當與定義擴充方法的類型搭配使用時 using static ,擴充方法會在其目標類型上變成可用(方法的類型 this )。 例如, using static System.Linq.Enumerable 可以用來擴充物件的 API IEnumerable<T> ,而不引進所有 LINQ 類型:

using static System.Linq.Enumerable;
using static System.String;

class Program
{
    static void Main()
    {
        var values = new int[] { 1, 2, 3, 4 };
        var evenValues = values.Where (i => i % 2 == 0);
        System.Console.WriteLine (Join(",", evenValues));
    }
}

上一個範例示範行為的差異:擴充方法 Enumerable.Where 與陣列相關聯,而靜態方法 String.Join 可以呼叫而不參考 String 類型。

nameof 表達式

有時候,您想要參考您指定變數或欄位的名稱。 在 C# 6 中, nameof(someVariableOrFieldOrType) 會傳回字串 "someVariableOrFieldOrType"。 例如,擲回 ArgumentException 時,您很可能想要將哪個自變數命名為無效:

throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))

表達式的主要 nameof 優點是它們經過類型檢查,且與工具支援的重構相容。 在用來動態關聯型別的情況下string,特別歡迎表達式的類型nameof檢查。 例如,在 iOS string 中,是用來指定 用來在 中UITableView建立物件原型UITableViewCell的類型。 nameof 可以保證此關聯不會因為拼錯或草率重構而失敗:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
    var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
    cell.TextLabel.Text = objects [indexPath.Row].ToString ();
    return cell;
}

雖然您可以將限定名稱傳遞至 nameof,但只會傳回最後一個專案(最後一個 .之後)。 例如,您可以在 Xamarin.Forms 中新增數據系結:

var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));

對的兩個呼叫 SetBinding 會傳遞相同的值: nameof(ReactiveType.StringField)"StringField",而不是 "ReactiveType.StringField" 您一開始預期的那樣。

Null 條件運算符

C# 的先前更新引進了可為 Null 型別和 Null 聯合運算子 ?? 的概念,以減少處理可為 Null 值時的重複使用程式碼數量。 C# 6 會以 「null 條件運算子」 ?.繼續此主題。 在表達式右側的物件上使用時,如果物件不是 nullnull ,則 null 條件運算符會傳回成員值,否則:

var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]

(和 length0length1 都推斷為 型別 int?

上一個範例中的最後一行顯示 ? null 條件運算符與 ?? null 聯合運算子的組合。 新的 C# 6 null 條件運算子會在陣列的第 2 個元素上傳回 null ,此時 Null 聯合運算符會開始,並提供 0 給 lengths 數位(當然,這是適當的問題特定)。

Null 條件運算子應該大幅減少許多許多應用程式所需的重複使用 Null 檢查數量。

Null 條件運算符有一些限制,因為模棱兩可。 您無法使用括號化自變數清單立即追蹤 ? ,因為您可能想要使用委派執行:

SomeDelegate?("Some Argument") // Not allowed

不過, Invoke 可用來將 與自變數清單分開 ? ,而且對重複使用的 -checking 區塊仍具有明顯的改善 null

public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);
    return true;
}

字串內插補點

String.Format式傳統上使用索引做為格式字串中的佔位元,例如 。 String.Format("Expected: {0} Received: {1}.", expected, received 當然,新增值一律牽涉到計算自變數、重新編號佔位元元,以及在自變數清單中正確順序插入新自變數的麻煩小任務。

C# 6 的新字串插補功能可大幅改善 String.Format。 現在,您可以直接在前面加上 $的字串中命名變數。 例如:

$"Expected: {expected} Received: {received}."

當然,變數會經過檢查,而且拼錯或非可用的變數會導致編譯程序錯誤。

佔位元不需要是簡單的變數,可以是任何表達式。 在這些佔位元元中,您可以使用引號 而不 逸出這些引號。 例如,請注意 "s" 下列中的 :

var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"

字串插補支援 的 String.Format對齊和格式化語法。 就像您先前撰寫 {index, alignment:format}的一樣,在 C# 6 中撰寫 {placeholder, alignment:format}

using static System.Linq.Enumerable;
using System;

class Program
{
    static void Main ()
    {
        var values = new int[] { 1, 2, 3, 4, 12, 123456 };
        foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
            Console.WriteLine (s);
        }
    Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
    }
}

結果為:

The value is       1.00.
The value is       2.00.
The value is       3.00.
The value is       4.00.
The value is      12.00.
The value is 123,456.00.
Minimum is 1.00.

字串插補是語法糖: String.Format它不能與字串常值搭配 @"" 使用,而且與 不相容 const,即使未使用佔位元:

const string s = $"Foo"; //Error : const requires value

在使用字串插補建函式自變數的常見使用案例中,您仍然需要小心逸出、編碼和文化特性問題。 當然,SQL 和 URL 查詢對於清理至關重要。 如同 , String.Format字串插補會使用 CultureInfo.CurrentCulture。 使用 CultureInfo.InvariantCulture 稍微多一點字:

Thread.CurrentThread.CurrentCulture  = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"

初始化

C# 6 提供一些簡潔的方式來指定屬性、欄位和成員。

自動屬性初始化

自動屬性現在可以以與欄位相同的簡潔方式初始化。 不可變的自動屬性只能使用 getter 撰寫:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;

在建構函式中,您可以設定僅限 getter 自動屬性的值:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;
    public string Description { get; }

    public ToDo (string description)
    {
        this.Description = description; //Can assign (only in constructor!)
    }

自動屬性的初始化既是一般節省空間的功能,也是希望強調其物件不變性的開發人員的福音。

索引初始設定式

C# 6 引進索引初始化表達式,可讓您在具有索引器的類型中設定索引鍵和值。 一般而言,這是針對 Dictionary樣式數據結構:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = new NSMutableDictionary {
        ["Created"] = NSDate.Now,
        ["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
        ["Task"] = Description
    };
    UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
    statusLabel.SetText ("Check phone");
}

表達式主體函式成員

Lambda 函式有數個優點,其中一項只是節省空間。 同樣地,表達式主體類別成員可讓小型函式比舊版 C# 6 更簡潔地表示。

表達式主體函式成員會使用 Lambda 箭頭語法,而不是傳統的區塊語法:

public override string ToString () => $"{FirstName} {LastName}";

請注意,Lambda 箭頭語法不會使用明確的 return。 對於傳回 void的函式,表達式也必須是 語句:

public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");

表達式主體成員仍然受限於方法所支援但不受屬性支持的規則 async

//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;

例外狀況

沒有兩種方式:例外狀況處理很難正確。 C# 6 的新功能讓例外狀況處理更具彈性且一致。

例外狀況篩選條件

根據定義,例外狀況會在不尋常的情況下發生,而且可能會很難對特定類型例外狀況的所有方式進行推理和程序代碼。 C# 6 引進了使用運行時間評估篩選來保護執行處理程式的功能。 這是藉由在一般catch(ExceptionType)宣告之後新增when (bool)模式來完成。 在下列情況下,篩選會區分與 參數相關的 date 剖析錯誤,而不是其他剖析錯誤。

public void ExceptionFilters(string aFloat, string date, string anInt)
{
    try
    {
        var f = Double.Parse(aFloat);
        var d = DateTime.Parse(date);
        var n = Int32.Parse(anInt);
    } catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
        Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
    } catch (FormatException x) {
        Console.WriteLine ("Problem parsing some other argument");
    }
}

await in catch...最後。。。

async C# 5 中引進的功能是語言的遊戲變更器。 在 C# 5 中,await不允許 在 和 finally 區塊中catch,因為功能的值async/await很煩人。 C# 6 會移除這項限制,讓異步結果一致地透過程式等候,如下列代碼段所示:

async void SomeMethod()
{
    try {
        //...etc...
    } catch (Exception x) {
        var diagnosticData = await GenerateDiagnosticsAsync (x);
        Logger.log (diagnosticData);
    } finally {
        await someObject.FinalizeAsync ();
    }
}

摘要

C# 語言會持續演進,讓開發人員更具生產力,同時提升良好作法和支援工具。 本檔提供 C# 6 中新語言功能的概觀,並簡要示範如何使用它們。