C# 6 的新功能What's New in C# 6

C# 6.0 版包含許多功能,能提升開發人員的產能。The 6.0 release of C# contained many features that improve productivity for developers. 這些功能的整體影響是您可撰寫更簡潔且更具可讀性的程式碼,。The overall effect of these features is that you write more concise code that is also more readable. 語法包含許多常見做法的較少繁瑣細節。The syntax contains less ceremony for many common practices. 繁瑣細節較少時比較容易看出設計目的。It's easier to see the design intent with less ceremony. 徹底了解這些功能,可讓您提升生產力,並撰寫更容易閱讀的程式碼。Learn these features well, and you'll be more productive and write more readable code. 您可以更專注於您的功能,而不是語言的建構。You can concentrate more on your features than on the constructs of the language.

本文的其餘部分將概述每項功能,並提供連結以探索每項功能。The rest of this article provides an overview of each of these features, with a link to explore each feature. 您也可以在<教學課程>一節的 C# 6 互動式探索中探索這些功能。You can also explore the features in an interactive exploration on C# 6 in the tutorials section.

唯讀 Auto 屬性Read-only auto-properties

「唯讀 Auto 屬性」** 提供更簡潔的語法,來建立不可變的類型。Read-only auto-properties provide a more concise syntax to create immutable types. 您只需要用 get 存取子宣告 Auto 屬性︰You declare the auto-property with only a get accessor:

public string FirstName { get; }
public string LastName { get;  }

FirstNameLastName 屬性只能在相同類別的建構函式主體中設定︰The FirstName and LastName properties can be set only in the body of the constructor of the same class:

public Student(string firstName, string lastName)
{
    if (IsNullOrWhiteSpace(lastName))
        throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
    FirstName = firstName;
    LastName = lastName;
}

嘗試在另一個方法設定 LastName 會產生 CS0200 編譯錯誤︰Trying to set LastName in another method generates a CS0200 compilation error:

public class Student
{
    public string LastName { get;  }

    public void ChangeName(string newLastName)
    {
        // Generates CS0200: Property or indexer cannot be assigned to -- it is read only
        LastName = newLastName;
    }
}

這項功能可真正達到建立不可變類型的語言支援,並使用更精簡且方便的 Auto 屬性語法。This feature enables true language support for creating immutable types and uses the more concise and convenient auto-property syntax.

如果新增這個語法不會移除可存取的方法,那麼這就是二進位相容變更If adding this syntax doesn't remove an accessible method, it's a binary compatible change.

Auto 屬性初始設定式Auto-property initializers

「Auto 屬性初始設定式」** 可以讓您宣告 Auto 屬性的初始值作為屬性宣告的一部分。Auto-property initializers let you declare the initial value for an auto-property as part of the property declaration.

public ICollection<double> Grades { get; } = new List<double>();

Grades 成員會在宣告的位置初始化。The Grades member is initialized where it's declared. 這可讓您更輕鬆地只執行初始化一次。That makes it easier to perform the initialization exactly once. 初始化是屬性宣告的一部分,如此能更容易讓儲存區配置與 Student 物件的公用介面相等。The initialization is part of the property declaration, making it easier to equate the storage allocation with the public interface for Student objects.

具有運算式主體的函式成員Expression-bodied function members

您撰寫的許多成員都是單一陳述式,而這些陳述式可能是單一運算式。Many members that you write are single statements that could be single expressions. 請改為撰寫運算式主體成員。Write an expression-bodied member instead. 其適用於方法和唯讀屬性。It works for methods and read-only properties. 例如,ToString() 覆寫通常是絕佳的候選︰For example, an override of ToString() is often a great candidate:

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

您也可以將此語法用於唯讀屬性:You can also use this syntax for read-only properties:

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

將現有成員變更為運算式主體成員是二進位相容變更Changing an existing member to an expression bodied member is a binary compatible change.

使用靜態using static

「使用靜態」** 增強功能可讓您匯入單一類別的靜態方法。The using static enhancement enables you to import the static methods of a single class. 您指定正在使用的類別︰You specify the class you're using:

using static System.Math;

Math 不包含任何執行個體方法。The Math does not contain any instance methods. 您也可以使用 using static 為同時具有靜態和執行個體方法的類別匯入類別的靜態方法。You can also use using static to import a class' static methods for a class that has both static and instance methods. 其中一個最有用的範例是 StringOne of the most useful examples is String:

using static System.String;

注意

您必須在靜態 using 陳述式中使用完整的類別名稱 System.StringYou must use the fully qualified class name, System.String in a static using statement. 不能改用 string 關鍵字。You cannot use the string keyword instead.

如果從 static using 陳述式匯入,擴充方法只有在使用擴充方法引動過程語法來呼叫時才會在範圍中。When imported from a static using statement, extension methods are only in scope when called using the extension method invocation syntax. 它們在作為靜態方法呼叫時,則不在範圍中。They aren't in scope when called as a static method. 您會經常在 LINQ 查詢中看到此情況。You'll often see this in LINQ queries. 您可以藉由匯入 EnumerableQueryable 來匯入 LINQ 模式。You can import the LINQ pattern by importing Enumerable, or Queryable.

using static System.Linq.Enumerable;

您通常會使用擴充方法引動過程運算式來呼叫擴充方法。You typically call extension methods using extension method invocation expressions. 在您使用靜態方法呼叫語法來呼叫它們的少數情況下,新增類別名稱可解決語意模糊問題。Adding the class name in the rare case where you call them using static method call syntax resolves ambiguity.

static using 指示詞也會匯入任何巢狀型別。The static using directive also imports any nested types. 您可以參照任何巢狀型別而無限定性條件。You can reference any nested types without qualification.

Null 條件運算子Null-conditional operators

「Null 條件運算子」** 讓 Null 檢查更容易且流暢。The null conditional operator makes null checks much easier and fluid. 請將成員存取 . 取代為 ?.Replace the member access . with ?.:

var first = person?.FirstName;

在上述範例中,如果 person 物件為 null,變數 first 便會被指派 nullIn the preceding example, the variable first is assigned null if the person object is null. 否則,會為其指派 FirstName 屬性的值。Otherwise, it is assigned the value of the FirstName property. 最重要的是,?. 表示當 person 變數是 null 時,這行程式碼不會產生 NullReferenceExceptionMost importantly, the ?. means that this line of code doesn't generate a NullReferenceException if the person variable is null. 相反地,它會進行最少運算並傳回 nullInstead, it short-circuits and returns null. 您也可以使用 Null 條件運算子來進行陣列或索引子存取。You can also use a null conditional operator for array or indexer access. 請在索引運算式中將 [] 取代為 ?[]Replace [] with ?[] in the index expression.

不論 person 的值為何,下列運算式都會傳回 stringThe following expression returns a string, regardless of the value of person. 您經常將這個建構與「Null 聯合」** 運算子一起使用,以便在其中一個屬性為 null 時指派預設值。You often use this construct with the null coalescing operator to assign default values when one of the properties is null. 當運算式進行最少運算時,傳回的 null 值型別會改變以符合完整的運算式。When the expression short-circuits, the null value returned is typed to match the full expression.

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

您也可以使用 ?. 來進行方法的條件式叫用。You can also use ?. to conditionally invoke methods. 成員函式搭配 Null 條件運算子的最常見用法,是安全地叫用可能為 null 的委派 (或事件處理常式)。The most common use of member functions with the null conditional operator is to safely invoke delegates (or event handlers) that may be null. 您將使用 ?. 運算子呼叫委派的 Invoke 方法來存取成員。You'll call the delegate's Invoke method using the ?. operator to access the member. 如需範例,請參閱委派模式一文。You can see an example in the delegate patterns article.

?. 運算子的規則確保運算子的左邊只會評估一次。The rules of the ?. operator ensure that the left-hand side of the operator is evaluated only once. 它可啟用許多慣用句,包括使用事件處理常式的下列範例:It enables many idioms, including the following example using event handlers:

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

確保左側只評估一次也可讓您在 ?. 的左側使用任何運算式,包括方法呼叫Ensuring that the left side is evaluated only once also enables you to use any expression, including method calls, on the left side of the ?.

字串插補String interpolation

使用 C# 6,新的字串插補功能可讓您將運算式內嵌在字串中。With C# 6, the new string interpolation feature enables you to embed expressions in a string. 只需要在字串前面加上 $,並在 {} 之間使用運算式而不是序數:Simply preface the string with $and use expressions between { and } instead of ordinals:

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

這個範例將屬性用於替代的運算式。This example uses properties for the substituted expressions. 您可以使用任何運算式。You can use any expression. 比方說,您可能在內插補點的過程中計算某學生的平均成積點︰For example, you could compute a student's grade point average as part of the interpolation:

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

前一行程式碼會將 Grades.Average() 的值格式化為具有兩個小數位數的浮點數。The preceding line of code formats the value for Grades.Average() as a floating-point number with two decimal places.

通常,您可能需要格式化使用特定文化特性產生的字串。Often, you may need to format the string produced using a specific culture. 您會利用字串插補所產生的物件可隱含轉換為 System.FormattableString 的事實。You use the fact that the object produced by a string interpolation can be implicitly converted to System.FormattableString. FormattableString 執行個體包含複合格式字串,以及在將其轉換為字串之前,評估運算式的結果。The FormattableString instance contains the composite format string and the results of evaluating the expressions before converting them to strings. 使用 FormattableString.ToString(IFormatProvider) 方法以指定設定字串格式時的文化特性。Use the FormattableString.ToString(IFormatProvider) method to specify the culture when formatting a string. 下列範例會使用德國 (de-DE) 文化特性產生字串。The following example produces a string using the German (de-DE) culture. (根據預設,德國文化特性會使用 ',' 字元作為十進位分隔符號,並使用 '. ' 字元作為千位分隔符號。)(By default, the German culture uses the ',' character for the decimal separator, and the '.' character as the thousands separator.)

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

若要開始使用字串插補,請參閱 C# 中的字串插補互動式教學課程、字串插補一文,以及 C# 中的字串插補教學課程。To get started with string interpolation, see the String interpolation in C# interactive tutorial, the String interpolation article, and the String interpolation in C# tutorial.

例外狀況篩選條件Exception filters

例外狀況篩選條件是判斷指定 catch 子句何時應該套用的子句。Exception Filters are clauses that determine when a given catch clause should be applied. 如果例外狀況篩選條件所用的運算式評估為 true,catch 子句便會對例外狀況執行其一般處理。If the expression used for an exception filter evaluates to true, the catch clause performs its normal processing on an exception. 如果運算式評估為 false,則會略過 catch 子句。If the expression evaluates to false, then the catch clause is skipped. 用法之一是檢查例外狀況的相關資訊,以判斷 catch 子句是否可以處理例外狀況︰One use is to examine information about an exception to determine if a catch clause can process the exception:

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";
        }
    }
}

nameof 運算式The nameof expression

nameof 運算式評估為符號的名稱。The nameof expression evaluates to the name of a symbol. 每當您需要變數、屬性或成員欄位的名稱時,這是讓工具能運作的好方法。It's a great way to get tools working whenever you need the name of a variable, a property, or a member field. nameof 最常見的其中一個用途是提供造成例外狀況的符號名稱︰One of the most common uses for nameof is to provide the name of a symbol that caused an exception:

if (IsNullOrWhiteSpace(lastName))
    throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));

另一個用途是實作 INotifyPropertyChanged 介面的 XAML 型應用程式:Another use is with XAML-based applications that implement the INotifyPropertyChanged interface:

public string LastName
{
    get { return lastName; }
    set
    {
        if (value != lastName)
        {
            lastName = value;
            PropertyChanged?.Invoke(this,
                new PropertyChangedEventArgs(nameof(LastName)));
        }
    }
}
private string lastName;

Catch 和 Finally 區塊中的 AwaitAwait in Catch and Finally blocks

C# 5 對於您可以放置 await 運算式的位置有數個限制。C# 5 had several limitations around where you could place await expressions. 透過 C# 6,您現在可以在 catchfinally 運算式中使用 awaitWith C# 6, you can now use await in catch or finally expressions. 這最常用於記錄情節︰This is most often used with logging scenarios:

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();
    }
}

catchfinally 子句內新增 await 支援的實作詳細資料,可確保行為與同步程式碼的行為一致。The implementation details for adding await support inside catch and finally clauses ensure that the behavior is consistent with the behavior for synchronous code. catchfinally 子句中執行的程式碼擲回時,執行會在下個周圍區塊中尋找適合的 catch 子句。When code executed in a catch or finally clause throws, execution looks for a suitable catch clause in the next surrounding block. 如果有目前的例外狀況,該例外狀況將會遺失。If there was a current exception, that exception is lost. catchfinally 子 句中等候的運算式也會發生相同的情況︰搜尋適合的 catch,目前的例外狀況 (如果有的話) 將會遺失。The same happens with awaited expressions in catch and finally clauses: a suitable catch is searched for, and the current exception, if any, is lost.

注意

此行為是建議您小心撰寫 catchfinally 子句的原因,以避免產生新的例外狀況。This behavior is the reason it's recommended to write catch and finally clauses carefully, to avoid introducing new exceptions.

使用索引子初始化關聯集合Initialize associative collections using indexers

「索引初始設定式」** 是讓集合初始設定式與索引使用方式更一致的兩個功能之一。Index Initializers is one of two features that make collection initializers more consistent with index usage. 在舊版的 C# 中,您可以在索引鍵值/組前後新增大括弧,藉以搭配序列樣式集合 (包括 Dictionary<TKey,TValue>) 來使用「集合初始設定式」**:In earlier releases of C#, you could use collection initializers with sequence style collections, including Dictionary<TKey,TValue>, by adding braces around key and value pairs:

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 方法接受一個以上的引數。You can use them with Dictionary<TKey,TValue> collections and other types where the accessible Add method accepts more than one argument. 新的語法支援使用集合中的索引進行指派:The new syntax supports assignment using an index into the collection:

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."
};

這項功能表示可以使用類似於已可供數個版本的序列容器使用的語法,將關聯容器初始化。This feature means that associative containers can be initialized using syntax similar to what's been in place for sequence containers for several versions.

集合初始設定式中的擴充 Add 方法Extension Add methods in collection initializers

能夠輕鬆進行集合初始設定的另一個功能,是可以針對 Add 方法使用「擴充方法」**。Another feature that makes collection initialization easier is the ability to use an extension method for the Add method. 新增這項功能是為了與 Visual Basic 相當。This feature was added for parity with Visual Basic. 當您的自訂集合類別具有不同名稱的方法,可以在語意上新增項目時,此功能最實用。The feature is most useful when you have a custom collection class that has a method with a different name to semantically add new items.

改進的多載解析Improved overload resolution

您可能不會發現最後這項功能。This last feature is one you probably won't notice. 在有些建構中,舊版 C# 編譯器可能會發現一些涉及 Lambda 運算式的方法呼叫為模稜兩可。There were constructs where the previous version of the C# compiler may have found some method calls involving lambda expressions ambiguous. 請參考下列方法:Consider this method:

static Task DoThings()
{
     return Task.FromResult(0);
}

在舊版的 C# 中,使用方法群組語法呼叫該方法會失敗︰In earlier versions of C#, calling that method using the method group syntax would fail:

Task.Run(DoThings);

舊版的編譯器無法正確分辨 Task.Run(Action)Task.Run(Func<Task>())The earlier compiler couldn't distinguish correctly between Task.Run(Action) and Task.Run(Func<Task>()). 在舊版中,您必須使用 Lambda 運算式作為引數︰In previous versions, you'd need to use a lambda expression as an argument:

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

C# 6 編譯器可正確判斷 Task.Run(Func<Task>()) 是較好的選擇。The C# 6 compiler correctly determines that Task.Run(Func<Task>()) is a better choice.

deterministic 編譯器選項Deterministic compiler output

-deterministic 選項會指示編譯器針對相同原始程式檔 的後續編譯產生位元組對位元組的相同輸出組件。The -deterministic option instructs the compiler to produce a byte-for-byte identical output assembly for successive compilations of the same source files.

根據預設,每次編譯會針對每次編譯產生唯一的輸出。By default, every compilation produces unique output on each compilation. 編譯器會新增時間戳記,以及從亂數產生的 GUID。The compiler adds a timestamp, and a GUID generated from random numbers. 如果您想要比較位元組對位元組的輸出,以確保組建的一致性,您可以使用此選項。You use this option if you want to compare the byte-for-byte output to ensure consistency across builds.

如需詳細資訊,請參閱 -deterministic 編譯器選項文章。For more information, see the -deterministic compiler option article.