C # 7.0 到 c # 7.3 的新功能What's new in C# 7.0 through C# 7.3

C # 7.0 到 c # 7.3 針對您使用 c # 的開發體驗引進了許多功能和累加的改進。C# 7.0 through C# 7.3 brought a number of features and incremental improvements to your development experience with C#. 本文概要說明新的語言功能和編譯器選項。This article provides an overview of the new language features and compiler options. 描述說明 c # 7.3 的行為,這是 .NET Framework 架構應用程式所支援的最新版本。The descriptions describe the behavior for C# 7.3, which is the most recent version supported for .NET Framework-based applications.

語言版本選取設定專案是使用 c # 7.1 新增的,可讓您在專案檔中指定編譯器語言版本。The language version selection configuration element was added with C# 7.1, which enables you to specify the compiler language version in your project file.

C # 7.0-7.3 將這些功能和主題新增至 c # 語言:C# 7.0-7.3 adds these features and themes to the C# language:

  • 元組和捨棄Tuples and discards
    • 您可以建立包含多個公用欄位的輕量、未具名的類型。You can create lightweight, unnamed types that contain multiple public fields. 編譯器和 IDE 工具了解這些類型的語意。Compilers and IDE tools understand the semantics of these types.
    • 捨棄是當您不在意指派的值時,於指派內使用的僅限寫入且暫時之變數。Discards are temporary, write-only variables used in assignments when you don't care about the value assigned. 解構 Tuple 及使用者定義型別,以及使用 out 參數呼叫方法時,捨棄最為實用。They're most useful when deconstructing tuples and user-defined types, as well as when calling methods with out parameters.
  • 模式比對Pattern Matching
    • 您可以建立以任意類型和這些類型成員的值為基礎的分支邏輯。You can create branching logic based on arbitrary types and values of the members of those types.
  • async``Main方法async Main method
    • 應用程式的進入點允許使用async修飾詞。The entry point for an application can have the async modifier.
  • 區域函數Local Functions
    • 您可以將函式巢狀放置在其他函式內,限制其範圍和可視性。You can nest functions inside other functions to limit their scope and visibility.
  • 更多運算式主體成員More expression-bodied members
    • 可以使用運算式來撰寫的成員清單已經增長。The list of members that can be authored using expressions has grown.
  • throw 表達 式throw Expressions
    • 您可以在程式碼建構中擲回例外狀況 (因為先前 throw 是陳述式,所以您無法這麼做)。You can throw exceptions in code constructs that previously weren't allowed because throw was a statement.
  • default 常值運算式default literal expressions
    • 目標類型可以推斷時,可以在預設值運算式中使用預設常值運算式。You can use default literal expressions in default value expressions when the target type can be inferred.
  • 數值常值的語法增強功能Numeric literal syntax improvements
    • 新的語彙基元改善了數值常數的可讀性。New tokens improve readability for numeric constants.
  • out 變數out variables
    • 您可以宣告 out 內嵌值作為使用它們之方法的引數。You can declare out values inline as arguments to the method where they're used.
  • 非後置具名引數Non-trailing named arguments
    • 具名引數之後可以接著位置引數。Named arguments can be followed by positional arguments.
  • private protected 存取修飾詞private protected access modifier
    • 您可利用 private protected 存取修飾詞,存取相同組件中的衍生類別。The private protected access modifier enables access for derived classes in the same assembly.
  • 改進的多載解析Improved overload resolution
    • 解決多載解析不明確的新規則。New rules to resolve overload resolution ambiguity.
  • 撰寫安全、有效率之程式碼的技巧Techniques for writing safe efficient code
    • 此為語法改進功能組合,其可讓使用參考語意進行實質型別作業變得可能。A combination of syntax improvements that enable working with value types using reference semantics.

最後,編譯器有新的選項:Finally, the compiler has new options:

  • -refout 以及 -refonly 該控制項 參考元件的產生-refout and -refonly that control reference assembly generation.
  • -publicsign,用來啟用組件的開放原始碼軟體 (OSS) 簽署。-publicsign to enable Open Source Software (OSS) signing of assemblies.
  • -pathmap,用來提供來源目錄的對應。-pathmap to provide a mapping for source directories.

此文章的其餘部分將概述各個功能。The remainder of this article provides an overview of each feature. 針對每項功能,您將瞭解其背後的原因和語法。For each feature, you'll learn the reasoning behind it and the syntax. 您可以使用 dotnet try 全域工具,在您的環境中探索這些功能:You can explore these features in your environment using the dotnet try global tool:

  1. 安裝 dotnet-try 全域工具。Install the dotnet-try global tool.
  2. 複製 dotnet/try-samples 存放庫。Clone the dotnet/try-samples repository.
  3. 將目前目錄設為 try-samples 存放庫的 csharp7 子目錄。Set the current directory to the csharp7 subdirectory for the try-samples repository.
  4. 執行 dotnet tryRun dotnet try.

元組和捨棄Tuples and discards

C# 為類別和結構提供豐富的語法,可用來解釋您的設計目的。C# provides a rich syntax for classes and structs that is used to explain your design intent. 但有時候豐富的語法需要額外的工作才能得到最少的好處。But sometimes that rich syntax requires extra work with minimal benefit. 您可能經常撰寫需要包含多個資料項目之簡單結構的方法。You may often write methods that need a simple structure containing more than one data element. 為了支援這些案例,C# 已新增 TupleTo support these scenarios tuples were added to C#. Tuple 是輕量的資料結構,其中包含多個欄位來代表資料成員。Tuples are lightweight data structures that contain multiple fields to represent the data members. 這些欄位不會經過驗證,且您無法定義自己的方法。The fields aren't validated, and you can't define your own methods. C # 元組類型支援 ==!=C# tuple types support == and !=. 如需詳細資訊,For more information.

注意

Tuple 在 C# 7.0 之前即可使用,但效率不彰且沒有語言支援。Tuples were available before C# 7.0, but they were inefficient and had no language support. 這表示元組元素只能參考為 Item1Item2 等等。This meant that tuple elements could only be referenced as Item1, Item2 and so on. C# 7.0 加入了 Tuple 的語言支援,讓 Tuple 欄位的語意名稱能使用全新且更具效率的 Tuple 型別。C# 7.0 introduces language support for tuples, which enables semantic names for the fields of a tuple using new, more efficient tuple types.

您可以將值指派給每個成員,並選擇性地提供語意名稱給每個 Tuple 的成員,以建立 Tuple:You can create a tuple by assigning a value to each member, and optionally providing semantic names to each of the members of the tuple:

(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

namedLetters Tuple 包含稱為 AlphaBeta 的欄位。The namedLetters tuple contains fields referred to as Alpha and Beta. 這些名稱只會在編譯時期存在,而且不會保留,例如在執行時間使用反映檢查元組時。Those names exist only at compile time and aren't preserved, for example when inspecting the tuple using reflection at run time.

在 Tuple 指派中,您也可以在指派的右邊,指定欄位的名稱︰In a tuple assignment, you can also specify the names of the fields on the right-hand side of the assignment:

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

有時候您可能會想要解除封裝已從方法傳回的 Tuple 成員。There may be times when you want to unpackage the members of a tuple that were returned from a method. 您可以藉由為 Tuple 中的每個值宣告不同變數來這麼做。You can do that by declaring separate variables for each of the values in the tuple. 這種解除封裝稱為解構 Tuple:This unpackaging is called deconstructing the tuple:

(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);

您也可以在 .NET 中為任何類型提供類似的解構。You can also provide a similar deconstruction for any type in .NET. 您可以將 Deconstruct 方法撰寫為類別的成員。You write a Deconstruct method as a member of the class. Deconstruct 方法為您想要擷取的每個屬性提供一組 out 引數。That Deconstruct method provides a set of out arguments for each of the properties you want to extract. 請考慮這個 Point 類別,它會提供 deconstructor 方法,擷取 XY 座標︰Consider this Point class that provides a deconstructor method that extracts the X and Y coordinates:

public class Point
{
    public Point(double x, double y)
        => (X, Y) = (x, y);

    public double X { get; }
    public double Y { get; }

    public void Deconstruct(out double x, out double y) =>
        (x, y) = (X, Y);
}

您可以藉由將 Point 指派給 Tuple 來擷取個別的欄位:You can extract the individual fields by assigning a Point to a tuple:

var p = new Point(3.14, 2.71);
(double X, double Y) = p;

當您初始化元組時,所使用的變數會與您想要用於元組專案的名稱相同:可從用來初始化元組的變數推斷元組元素的名稱:Many times when you initialize a tuple, the variables used for the right side of the assignment are the same as the names you'd like for the tuple elements: The names of tuple elements can be inferred from the variables used to initialize the tuple:

int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // element names are "count" and "label"

您可以在「 元組類型 」一文中深入瞭解這項功能。You can learn more about this feature in the Tuple types article.

通常在您解構元組或以 out 參數呼叫方法時,會強制您要定義變數,而您無須在意變數的值也沒有使用該值的打算。Often when deconstructing a tuple or calling a method with out parameters, you're forced to define a variable whose value you don't care about and don't intend to use. C# 新增了對捨棄的支援,來應付這種狀況。C# adds support for discards to handle this scenario. 捨棄是僅限寫入的變數,其名稱為 _ (底線字元);您可將所有想要捨棄的值指派到單一變數。A discard is a write-only variable whose name is _ (the underscore character); you can assign all of the values that you intend to discard to the single variable. 捨棄類似於未經指派的變數;和指派陳述式一樣,都不能用於程式碼中。A discard is like an unassigned variable; apart from the assignment statement, the discard can't be used in code.

下列情況中支援捨棄:Discards are supported in the following scenarios:

  • 解構元組或使用者定義型別時。When deconstructing tuples or user-defined types.
  • out 參數呼叫方法時。When calling methods with out parameters.
  • 執行 isswitch 陳述式的模式比對作業時。In a pattern matching operation with the is and switch statements.
  • 作為獨立識別項,當您想要將指派的值明確識別為捨棄時。As a standalone identifier when you want to explicitly identify the value of an assignment as a discard.

下列範例定義的 QueryCityDataForYears 方法,會傳回包含兩個不同年份之城市資料的 6 元組。The following example defines a QueryCityDataForYears method that returns a 6-tuple that contains data for a city for two different years. 範例中的方法呼叫只有對方法傳回的兩個母體有效,所以會在解構元組時,將元組中剩餘的值視作捨棄處理。The method call in the example is concerned only with the two population values returned by the method and so treats the remaining values in the tuple as discards when it deconstructs the tuple.

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

如需詳細資訊,請參閱捨棄For more information, see Discards.

模式比對Pattern matching

模式 比對是一組功能,可讓您在程式碼中以新的方式表示控制流程。Pattern matching is a set of features that enable new ways to express control flow in your code. 您可以針對其類型、值或其屬性的值來測試變數。You can test variables for their type, values or the values of their properties. 這些技術可建立更容易閱讀的程式碼流程。These techniques create more readable code flow.

模式比對支援 is 運算式和 switch 運算式。Pattern matching supports is expressions and switch expressions. 每個都讓您能檢查物件和其屬性,以判斷該物件是否符合搜尋的模式。Each enables inspecting an object and its properties to determine if that object satisfies the sought pattern. 您使用 when 關鍵字來指定其他規則給該模式。You use the when keyword to specify additional rules to the pattern.

is模式運算式會擴充熟悉的 is 運算子,以查詢其類型的物件,並在一個指令中指派結果。The is pattern expression extends the familiar is operator to query an object about its type and assign the result in one instruction. 下列程式碼會檢查變數是否為 int,如果是,則將其加入至目前的總和:The following code checks if a variable is an int, and if so, adds it to the current sum:

if (input is int count)
    sum += count;

上述的小範例示範 is 運算式的增強功能。The preceding small example demonstrates the enhancements to the is expression. 您可以對實值型別以及參考型別進行測試,而且您可以將成功的結果指派給正確型別的新變數。You can test against value types as well as reference types, and you can assign the successful result to a new variable of the correct type.

switch 比對運算式的語法很熟悉,是以已屬於 C# 語言一部分的 switch 陳述式為基礎。The switch match expression has a familiar syntax, based on the switch statement already part of the C# language. 更新的 switch 陳述式有數個新的建構:The updated switch statement has several new constructs:

  • switch 運算式的控管型別不再限於整數型別、Enum 型別、string,或對應至這些其中一種類型的可為 Null 的型別。The governing type of a switch expression is no longer restricted to integral types, Enum types, string, or a nullable type corresponding to one of those types. 可以使用任何型別。Any type may be used.
  • 您可以測試每個 case 標籤中的 switch 運算式型別。You can test the type of the switch expression in each case label. 如同使用 is 運算式,您可以將新的變數指派給該型別。As with the is expression, you may assign a new variable to that type.
  • 您可以加入 when 子句,以進一步測試該變數的條件。You may add a when clause to further test conditions on that variable.
  • case 標籤的順序現在很重要。The order of case labels is now important. 系統會執行要比對的第一個分支;其他則會略過。The first branch to match is executed; others are skipped.

下列程式碼可示範這些新功能:The following code demonstrates these new features:

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
    int sum = 0;
    foreach (var i in sequence)
    {
        switch (i)
        {
            case 0:
                break;
            case IEnumerable<int> childSequence:
            {
                foreach(var item in childSequence)
                    sum += (item > 0) ? item : 0;
                break;
            }
            case int n when n > 0:
                sum += n;
                break;
            case null:
                throw new NullReferenceException("Null found in sequence");
            default:
                throw new InvalidOperationException("Unrecognized type");
        }
    }
    return sum;
}
  • case 0: 是熟悉的常數模式。case 0: is the familiar constant pattern.
  • case IEnumerable<int> childSequence: 是一種型別模式。case IEnumerable<int> childSequence: is a type pattern.
  • case int n when n > 0: 是一種具有其他 when 條件的型別模式。case int n when n > 0: is a type pattern with an additional when condition.
  • case null: 是 Null 模式。case null: is the null pattern.
  • default: 是熟悉的預設案例。default: is the familiar default case.

從 C# 7.1 開始,isswitch 型別模式的模式運算式可能會有泛型型別參數的型別。Beginning with C# 7.1, the pattern expression for is and the switch type pattern may have the type of a generic type parameter. 在檢查可能是 structclass 型別的型別,以及您想要避免 boxing 時,這可能非常有用。This can be most useful when checking types that may be either struct or class types, and you want to avoid boxing.

您可以在 C# 中的模式比對中深入了解模式比對。You can learn more about pattern matching in Pattern Matching in C#.

非同步主要Async main

「非同步主要」** 方法可讓您在 Main 方法中使用 awaitAn async main method enables you to use await in your Main method. 在過去您必須這樣寫:Previously you would need to write:

static int Main()
{
    return DoAsyncWork().GetAwaiter().GetResult();
}

現在您可以這樣寫:You can now write:

static async Task<int> Main()
{
    // This could also be replaced with the body
    // DoAsyncWork, including its await expressions:
    return await DoAsyncWork();
}

如果您的程式不會傳回結束代碼,您可以宣告傳回 TaskMain 方法:If your program doesn't return an exit code, you can declare a Main method that returns a Task:

static async Task Main()
{
    await SomeAsyncMethod();
}

您可以在程式設計指南的非同步主要文章中閱讀更多詳細資料。You can read more about the details in the async main article in the programming guide.

區域函式Local functions

類別的許多設計都包括從唯一一個位置呼叫的方法。Many designs for classes include methods that are called from only one location. 這些額外的私用方法會使每一種方法維持小而聚焦。These additional private methods keep each method small and focused. 「區域函式」** 可讓您在另一個方法的內容中宣告方法。Local functions enable you to declare methods inside the context of another method. 區域函式可讓類別讀者輕鬆查看區域方法只會從其宣告的內容中呼叫。Local functions make it easier for readers of the class to see that the local method is only called from the context in which it is declared.

有兩個常見的區域函式使用案例︰公用迭代器方法和公用非同步方法。There are two common use cases for local functions: public iterator methods and public async methods. 這兩種方法都會產生程式碼,比程式設計人員想像更晚才報告錯誤。Both types of methods generate code that reports errors later than programmers might expect. 在迭代器方法中,任何例外狀況只會在呼叫列舉傳回序列的程式碼時觀察到。In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. 在非同步方法中,任何例外狀況只會在傳回的 Task 等候時觀察到。In async methods, any exceptions are only observed when the returned Task is awaited. 下列範例示範使用區域函式分隔參數驗證和迭代器實作:The following example demonstrates separating parameter validation from the iterator implementation using a local function:

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

相同的技巧也可以運用在 async 方法,以確保在非同步工作開始之前,會擲回引數驗證所引發的例外狀況︰The same technique can be employed with async methods to ensure that exceptions arising from argument validation are thrown before the asynchronous work begins:

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

現在支援此語法:This syntax is now supported:

[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }

屬性 SomeThingAboutFieldAttribute 套用至編譯器針對 SomeProperty 產生的支援欄位。The attribute SomeThingAboutFieldAttribute is applied to the compiler generated backing field for SomeProperty. 如需詳細資訊,請參閱 C# 程式設計指南中的屬性For more information, see attributes in the C# programming guide.

注意

某些區域函數所支援的設計也可以使用 lambda 運算式來完成。Some of the designs that are supported by local functions can also be accomplished using lambda expressions. 如需詳細資訊,請參閱 區域函數與 lambda 運算式For more information, see Local functions vs. lambda expressions.

更多運算式主體成員More expression-bodied members

C# 6 引進了成員函式的運算式主體成員和唯讀屬性。C# 6 introduced expression-bodied members for member functions, and read-only properties. C# 7.0 擴充了可以實作為運算式的允許成員。C# 7.0 expands the allowed members that can be implemented as expressions. 在 C# 7.0 中,您可以實作「建構函式」、「完成項」,以及「屬性」** 和「索引子」** 上的 getset 存取子。In C# 7.0, you can implement constructors, finalizers, and get and set accessors on properties and indexers. 下列程式碼將示範各項的範例:The following code shows examples of each:

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

注意

這個範例不需要完成項,但仍加以顯示以示範語法。This example does not need a finalizer, but it is shown to demonstrate the syntax. 除非為釋放 Unmanaged 資源所需,否則不應該在類別中實作完成項。You should not implement a finalizer in your class unless it is necessary to release unmanaged resources. 您也應該考慮使用 SafeHandle 類別而不是直接管理 Unmanaged 資源。You should also consider using the SafeHandle class instead of managing unmanaged resources directly.

這些運算式主體成員的新位置代表 C# 語言的重要里程碑︰這些功能是由參與開放原始碼 Roslyn 專案的社群成員所實作。These new locations for expression-bodied members represent an important milestone for the C# language: These features were implemented by community members working on the open-source Roslyn project.

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

throw 運算式Throw expressions

在 C# 中,throw 一直都是陳述式。In C#, throw has always been a statement. 因為 throw 是陳述式,而不是運算式,所以有您無法在其中使用它的 C# 建構。Because throw is a statement, not an expression, there were C# constructs where you couldn't use it. 這些包含條件運算式、Null 聯合運算式和一些 Lambda 運算式。These included conditional expressions, null coalescing expressions, and some lambda expressions. 新增運算式主體成員會新增 throw 運算式適用的更多位置。The addition of expression-bodied members adds more locations where throw expressions would be useful. 您可以撰寫任何這些結構,c # 7.0 引進了 擲回運算式So that you can write any of these constructs, C# 7.0 introduces throw expressions.

此新增可讓您更輕鬆地撰寫更多以運算式為基礎的程式碼。This addition makes it easier to write more expression-based code. 您不需要額外的陳述式就可以進行錯誤檢查。You don't need additional statements for error checking.

預設常值運算式Default literal expressions

預設常值運算式是預設值運算式的增強功能。Default literal expressions are an enhancement to default value expressions. 這些運算式會將變數初始化為預設值。These expressions initialize a variable to the default value. 在過去您必須這樣寫:Where you previously would write:

Func<string, bool> whereClause = default(Func<string, bool>);

您現在可以略過初始化右側的類型:You can now omit the type on the right-hand side of the initialization:

Func<string, bool> whereClause = default;

如需詳細資訊,請參閱預設運算子一文中的預設常值一節。For more information, see the default literal section of the default operator article.

數值常值的語法增強功能Numeric literal syntax improvements

錯譯數值常數,可能會使得在第一次閱讀它時,更加難以了解程式碼。Misreading numeric constants can make it harder to understand code when reading it for the first time. 位元遮罩或其他符號值容易遭到誤解。Bit masks or other symbolic values are prone to misunderstanding. C# 7.0 包含兩項新功能,可以用預期用途最容易閱讀的方式來撰寫數字︰「二進位常值」** 和「數字分隔符號」**。C# 7.0 includes two new features to write numbers in the most readable fashion for the intended use: binary literals, and digit separators.

當您在建立位元遮罩時,或是每當數字的二進位表示法構成最容易閱讀的程式碼時,請以二進位撰寫該數字︰For those times when you're creating bit masks, or whenever a binary representation of a number makes the most readable code, write that number in binary:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

常數開頭的 0b 表示數字是撰寫為二進位數字。The 0b at the beginning of the constant indicates that the number is written as a binary number. 二進位數可能會很長,因此藉由將作為數位分隔符號來查看位模式通常會更容易 _ ,如上述範例中的二進位常數所示。Binary numbers can get long, so it's often easier to see the bit patterns by introducing the _ as a digit separator, as shown in the binary constant in the preceding example. 千位分隔符號可以出現在常數的任何位置。The digit separator can appear anywhere in the constant. 若是以10為底數的數位,通常會使用它作為千位分隔符號。For base 10 numbers, it is common to use it as a thousands separator. 十六進位和二進位數值常值的開頭可能是 _Hex and binary numeric literals may begin with an _:

public const long BillionsAndBillions = 100_000_000_000;

千位分隔符號也可以搭配 decimalfloatdouble 類型使用︰The digit separator can be used with decimal, float, and double types as well:

public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

結合起來,您在宣告數值常數時可以有更多的可讀性。Taken together, you can declare numeric constants with much more readability.

out 變數out variables

out在 c # 7 中已改善支援參數的現有語法。The existing syntax that supports out parameters has been improved in C# 7. 您現在可以在方法呼叫的引數清單中宣告 out 變數,而不是撰寫個別的宣告陳述式︰You can now declare out variables in the argument list of a method call, rather than writing a separate declaration statement:

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Could not parse input");

您可能會想要明確指定變數的類型 out ,如上述範例所示。You may want to specify the type of the out variable for clarity, as shown in the preceding example. 不過,語言有支援使用隱含型別區域變數︰However, the language does support using an implicitly typed local variable:

if (int.TryParse(input, out var answer))
    Console.WriteLine(answer);
else
    Console.WriteLine("Could not parse input");
  • 程式碼更容易閱讀與理解。The code is easier to read.
    • 您可以在使用時宣告 out 變數,而不是在先前的程式程式碼上。You declare the out variable where you use it, not on a preceding line of code.
  • 不需要指派初始值。No need to assign an initial value.
    • 藉由在方法呼叫中使用所在宣告 out 變數,就不會在指派它之前意外使用它。By declaring the out variable where it's used in a method call, you can't accidentally use it before it is assigned.

加入 C# 7.0 中以允許 out 變數宣告的語法已經擴充,以包含欄位初始設定式、屬性初始設定式、建構函式初始設定式和查詢子句。The syntax added in C# 7.0 to allow out variable declarations has been extended to include field initializers, property initializers, constructor initializers, and query clauses. 它讓您能執行如下列範例的程式碼:It enables code such as the following example:

public class B
{
   public B(int i, out int j)
   {
      j = i;
   }
}

public class D : B
{
   public D(int i) : base(i, out var j)
   {
      Console.WriteLine($"The value of 'j' is {j}");
   }
}

非後置具名引數Non-trailing named arguments

當具名引數位於正確位置時,方法呼叫現在可以在位置引數前面使用具名引數。Method calls may now use named arguments that precede positional arguments when those named arguments are in the correct positions. 如需詳細資訊,請參閱 指名的和選擇性引數For more information, see Named and optional arguments.

private protected 存取修飾詞private protected access modifier

新的複合存取修飾詞:private protected 指出可藉由包含類別或在相同組件中宣告的衍生類別來存取成員。A new compound access modifier: private protected indicates that a member may be accessed by containing class or derived classes that are declared in the same assembly. protected internal 允許衍生類別或相同組件中的類別進行存取,而 private protected 則限制在相同組件中宣告的衍生類型進行存取。While protected internal allows access by derived classes or classes that are in the same assembly, private protected limits access to derived types declared in the same assembly.

如需詳細資訊,請參閱語言參考中的 存取 修飾詞。For more information, see access modifiers in the language reference.

改進的多載候選項目Improved overload candidates

在每個版本中,多載解析規則都會更新,以解決模稜兩可的方法引動過程具有「明確」選項的情況。In every release, the overload resolution rules get updated to address situations where ambiguous method invocations have an "obvious" choice. 此版本新增三個新的規則,以協助編譯器挑選明確的選項:This release adds three new rules to help the compiler pick the obvious choice:

  1. 當方法群組同時包含執行個體和靜態成員時,如果在沒有執行個體接收者或內容的情況下叫用方法,編譯器會捨棄執行個體成員。When a method group contains both instance and static members, the compiler discards the instance members if the method was invoked without an instance receiver or context. 如果使用執行個體接收者叫用方法,編譯器就會捨棄靜態成員。The compiler discards the static members if the method was invoked with an instance receiver. 沒有接收者時,編譯器只會在靜態內容中包含靜態成員,否則將同時包含靜態成員和執行個體成員。When there is no receiver, the compiler includes only static members in a static context, otherwise both static and instance members. 當無法確定接收者是執行個體或型別時,編譯器會包含兩者。When the receiver is ambiguously an instance or type, the compiler includes both. 無法使用隱含 this 執行個體接收者的靜態內容,包括未定義 this 的成員主體 (例如靜態成員),以及不能使用 this 的位置 (例如欄位初始設定式和建構函式初始設定式)。A static context, where an implicit this instance receiver cannot be used, includes the body of members where no this is defined, such as static members, as well as places where this cannot be used, such as field initializers and constructor-initializers.
  2. 當方法群組包含某些型別引數不滿足其限制式的泛型方法時,這些成員將會從候選集合中移除。When a method group contains some generic methods whose type arguments do not satisfy their constraints, these members are removed from the candidate set.
  3. 針對方法群組轉換,其傳回型別與委派的傳回型別不符合的候選方法,將會從集合中移除。For a method group conversion, candidate methods whose return type doesn't match up with the delegate's return type are removed from the set.

您只會注意到此變更,因為若確定何種方法較佳,就會發現模稜兩可的方法多載會有較少的編譯器錯誤。You'll only notice this change because you'll find fewer compiler errors for ambiguous method overloads when you are sure which method is better.

啟用更有效率的安全的程式碼Enabling more efficient safe code

您應該能夠安全地撰寫與不安全的程式碼一樣高效能的 C# 程式碼。You should be able to write C# code safely that performs as well as unsafe code. 安全的程式碼可避免一些錯誤類別,例如緩衝區溢位、偏離的指標和其他記憶體存取錯誤。Safe code avoids classes of errors, such as buffer overruns, stray pointers, and other memory access errors. 這些新功能擴充了可驗證安全的程式碼的功能。These new features expand the capabilities of verifiable safe code. 儘可能使用安全的建構來撰寫更多的程式碼。Strive to write more of your code using safe constructs. 這些功能都讓這變得更容易。These features make that easier.

下列新功能針對安全的程式碼支援較佳效能的佈景主題:The following new features support the theme of better performance for safe code:

  • 您可以在不釘選的情況下存取固定的欄位。You can access fixed fields without pinning.
  • 您可以重新指派 ref 區域變數。You can reassign ref local variables.
  • 您可以在 stackalloc 陣列上使用初始設定式。You can use initializers on stackalloc arrays.
  • 您可以搭配支援模式的任何型別使用 fixed 陳述式。You can use fixed statements with any type that supports a pattern.
  • 您可以使用其他的泛型限制式。You can use additional generic constraints.
  • 參數上的 in 修飾詞,可指定引數將以傳址方式傳遞,但不受呼叫的方法修改。The in modifier on parameters, to specify that an argument is passed by reference but not modified by the called method. in 修飾詞新增至引數是來源相容變更Adding the in modifier to an argument is a source compatible change.
  • 方法上 ref readonly 修飾詞的傳回內容,可指示方法要以傳址方式傳回其值,但不允許寫入該物件。The ref readonly modifier on method returns, to indicate that a method returns its value by reference but doesn't allow writes to that object. 如果將傳回項目指派給值。則新增 ref readonly 修飾詞是來源相容變更Adding the ref readonly modifier is a source compatible change, if the return is assigned to a value. readonly 修飾詞新增至現有 ref 傳回陳述式是不相容的變更Adding the readonly modifier to an existing ref return statement is an incompatible change. 需要呼叫端更新 ref 本機變數的宣告,以包含 readonly 修飾詞。It requires callers to update the declaration of ref local variables to include the readonly modifier.
  • readonly struct 宣告,可指示結構會固定,且應以 in 參數傳遞至其成員方法。The readonly struct declaration, to indicate that a struct is immutable and should be passed as an in parameter to its member methods. readonly 修飾詞新增至現有 struct 宣告是二進位相容變更Adding the readonly modifier to an existing struct declaration is a binary compatible change.
  • ref struct 宣告,可指示結構類型會直接存取受控記憶體,且一律會配置堆疊。The ref struct declaration, to indicate that a struct type accesses managed memory directly and must always be stack allocated. ref 修飾詞新增至現有 struct 宣告是不相容的變更Adding the ref modifier to an existing struct declaration is an incompatible change. ref struct 不能是類別的成員,或用於可能會在堆積上配置的其他位置。A ref struct cannot be a member of a class or used in other locations where it may be allocated on the heap.

您可以在撰寫安全、有效率的程式碼深入了解這些變更。You can read more about all these changes in Write safe efficient code.

Ref 區域變數和傳回Ref locals and returns

這項功能可以啟用使用和傳回在其他地方定義之變數參考的演算法。This feature enables algorithms that use and return references to variables defined elsewhere. 其中一個範例是使用大型矩陣,並尋找具有特定特性的單一位置。One example is working with large matrices, and finding a single location with certain characteristics. 下列方法會傳回矩陣中該儲存體的參考The following method returns a reference to that storage in the matrix:

public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

您可以將傳回值宣告為 ref,並在矩陣中修改該值,如下列程式碼所示:You can declare the return value as a ref and modify that value in the matrix, as shown in the following code:

ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);

C# 語言具備數個規則,可防止您濫用 ref 區域變數並傳回︰The C# language has several rules that protect you from misusing the ref locals and returns:

  • 您必須將 ref 關鍵字加入至方法特徵標記,以及某個方法中的所有 return 陳述式。You must add the ref keyword to the method signature and to all return statements in a method.
    • 如此可透過整個方法的參考,清除方法傳回。That makes it clear the method returns by reference throughout the method.
  • ref return 可能會指派給值變數,或 ref 變數。A ref return may be assigned to a value variable, or a ref variable.
    • 呼叫端會控制是否要複製傳回值。The caller controls whether the return value is copied or not. 指派傳回值時省略 ref 修飾詞表示呼叫端需要值的複本,而不是儲存體的參考。Omitting the ref modifier when assigning the return value indicates that the caller wants a copy of the value, not a reference to the storage.
  • 您無法將標準方法傳回值指派到 ref 區域變數。You can't assign a standard method return value to a ref local variable.
    • 這可杜絕類似 ref int i = sequence.Count(); 的陳述式That disallows statements like ref int i = sequence.Count();
  • 您無法將 ref 傳回給其存留期不超過方法執行的變數。You can't return a ref to a variable whose lifetime doesn't extend beyond the execution of the method.
    • 這表示您無法將參考傳回區域變數或具有類似範圍的變數。That means you can't return a reference to a local variable or a variable with a similar scope.
  • ref 區域變數及傳回值無法配合非同步方法使用。ref locals and returns can't be used with async methods.
    • 當非同步方法傳回時,編譯器無法確定參考的變數是否已設定為其最終的值。The compiler can't know if the referenced variable has been set to its final value when the async method returns.

新增 ref 區域變數和 ref 傳回能啟用更有效率的演算法,因為可以避免複製值,或是多次執行取值作業。The addition of ref locals and ref returns enables algorithms that are more efficient by avoiding copying values, or performing dereferencing operations multiple times.

ref 新增至傳回值是來源相容變更Adding ref to the return value is a source compatible change. 現有程式碼會編譯,但是 ref 傳回值是在指派時複製。Existing code compiles, but the ref return value is copied when assigned. 呼叫端必須將傳回值的儲存體更新為 ref 區域變數,將傳回項目儲存為參考。Callers must update the storage for the return value to a ref local variable to store the return as a reference.

現在,ref 區域變數可能會在初始化之後被重新指派,以參照不同的執行個體。Now, ref locals may be reassigned to refer to different instances after being initialized. 下列程式碼現在會編譯:The following code now compiles:

ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

如需詳細資訊,請參閱有關傳回的 refref 區域變數的文章,以及的相關文章 foreachFor more information, see the article on ref returns and ref locals, and the article on foreach.

如需詳細資訊,請參閱 ref 關鍵字一文。For more information, see the ref keyword article.

ref 條件運算式Conditional ref expressions

最後,條件運算式可能會產生參考結果,而不是實值結果。Finally, the conditional expression may produce a ref result instead of a value result. 例如,您可以撰寫下列程式碼,在兩個陣列的其中一個上,擷取對第一個元素的參考:For example, you would write the following to retrieve a reference to the first element in one of two arrays:

ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);

變數 rarrotherArr 中對第一個值的參考。The variable r is a reference to the first value in either arr or otherArr.

如需詳細資訊,請參閱語言參考中的條件運算子 (?:)For more information, see conditional operator (?:) in the language reference.

in 參數修飾詞in parameter modifier

in關鍵字會補充現有的 ref 和 out 關鍵字,以傳址方式傳遞引數。The in keyword complements the existing ref and out keywords to pass arguments by reference. in 關鍵字會指定以參考型式傳遞引數,但呼叫的方法不會修改值。The in keyword specifies passing the argument by reference, but the called method doesn't modify the value.

您可以宣告以傳值方式或唯讀參考傳遞的多載,如下列程式碼所示:You may declare overloads that pass by value or by readonly reference, as shown in the following code:

static void M(S arg);
static void M(in S arg);

前一個範例中的 by 值 (先) 多載比唯讀參考版本更好。The by value (first in the preceding example) overload is better than the by readonly reference version. 若要呼叫具有唯讀參考引數的版本,您呼叫方法時必須包含 in 修飾詞。To call the version with the readonly reference argument, you must include the in modifier when calling the method.

如需詳細資訊,請參閱 in 參數修飾詞上的文章。For more information, see the article on the in parameter modifier.

更多型別支援 fixed 陳述式More types support the fixed statement

fixed 陳述式支援一組有限的型別。The fixed statement supported a limited set of types. 從 C# 7.3 開始,包含傳回 ref Tref readonly TGetPinnableReference() 方法的任何型別都可能是 fixedStarting with C# 7.3, any type that contains a GetPinnableReference() method that returns a ref T or ref readonly T may be fixed. 新增此功能表示 fixed 可以與 System.Span<T> 和相關型別一起使用。Adding this feature means that fixed can be used with System.Span<T> and related types.

如需詳細資訊,請參閱語言參考中的 fixed 語句文章。For more information, see the fixed statement article in the language reference.

索引 fixed 欄位不需要釘選Indexing fixed fields does not require pinning

請考量此建構:Consider this struct:

unsafe struct S
{
    public fixed int myFixedField[10];
}

在舊版的 C# 中,您需要釘選變數,才能存取屬於 myFixedField 的其中一個整數。In earlier versions of C#, you needed to pin a variable to access one of the integers that are part of myFixedField. 現在,下列程式碼會進行編譯而不將變數 p 固定在個別的 fixed 陳述式內:Now, the following code compiles without pinning the variable p inside a separate fixed statement:

class C
{
    static S s = new S();

    unsafe public void M()
    {
        int p = s.myFixedField[5];
    }
}

變數 p 會存取 myFixedField 中的一個元素。The variable p accesses one element in myFixedField. 您不需要宣告個別的 int* 變數。You don't need to declare a separate int* variable. 您仍然需要 unsafe 內容。You still need an unsafe context. 在舊版的 C# 中,您需要宣告第二個固定指標:In earlier versions of C#, you need to declare a second fixed pointer:

class C
{
    static S s = new S();

    unsafe public void M()
    {
        fixed (int* ptr = s.myFixedField)
        {
            int p = ptr[5];
        }
    }
}

如需詳細資訊,請參閱有關 fixed 語句的文章。For more information, see the article on the fixed statement.

stackalloc 陣列支援初始設定式stackalloc arrays support initializers

您可以在初始化陣列時,指定陣列中元素的值:You've been able to specify the values for elements in an array when you initialize it:

var arr = new int[3] {1, 2, 3};
var arr2 = new int[] {1, 2, 3};

現在,相同的語法可以套用至使用 stackalloc 宣告的陣列:Now, that same syntax can be applied to arrays that are declared with stackalloc:

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

如需詳細資訊,請參閱 stackalloc 操作員文章。For more information, see the stackalloc operator article.

增強泛型限制式Enhanced generic constraints

您現在可以指定型別 System.EnumSystem.Delegate 作為型別參數的基底類別限制式。You can now specify the type System.Enum or System.Delegate as base class constraints for a type parameter.

您也可以使用新的 unmanaged 條件約束,指定型別參數必須是不可為 null 的 非受控型別。You can also use the new unmanaged constraint, to specify that a type parameter must be a non-nullable unmanaged type.

如需詳細資訊,請參閱 where 泛型條件約束的相關文章和類型參數的條件約束For more information, see the articles on where generic constraints and constraints on type parameters.

將這些條件約束新增至現有型別是不相容變更Adding these constraints to existing types is an incompatible change. 封閉式泛型型別可能不再符合這些新的條件約束。Closed generic types may no longer meet these new constraints.

通用的非同步傳回型別Generalized async return types

從非同步方法傳回 Task 物件可能會造成在特定路徑的效能瓶頸。Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task 是參考型別,因此使用它表示要配置物件。Task is a reference type, so using it means allocating an object. 當使用 async 修飾詞宣告的方法傳回快取的結果時,或是以同步方式完成,額外配置可能會在效能關鍵的程式碼區段中變成一項重要的時間成本。In cases where a method declared with the async modifier returns a cached result, or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. 如果這些配置發生在緊密迴圈中,它可能會變得成本很高。It can become costly if those allocations occur in tight loops.

新的語言功能表示 async 方法傳回型別不限於 TaskTask<T>voidThe new language feature means that async method return types aren't limited to Task, Task<T>, and void. 傳回的型別仍然必須滿足非同步模式,也就是表示 GetAwaiter 方法必須可存取。The returned type must still satisfy the async pattern, meaning a GetAwaiter method must be accessible. 作為一個具體的範例, ValueTask 類型已新增至 .net 以利用這個新的語言功能:As one concrete example, the ValueTask type has been added to .NET to make use of this new language feature:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

注意

您需要新增 NuGet 套件 >,才能 System.Threading.Tasks.Extensions 使用 ValueTask<TResult> 類型。You need to add the NuGet package System.Threading.Tasks.Extensions > in order to use the ValueTask<TResult> type.

這項增強功能最能幫助程式庫作者避免在效能關鍵的程式碼中配置 TaskThis enhancement is most useful for library authors to avoid allocating a Task in performance critical code.

新的編譯器選項New compiler options

新的編譯器選項支援 C# 程式的新組建和 DevOps 案例。New compiler options support new build and DevOps scenarios for C# programs.

參考組件產生Reference assembly generation

有兩個新編譯器選項會產生「僅參考的組件」**:-refout-refonlyThere are two new compiler options that generate reference-only assemblies: -refout and -refonly. 連結的文章將更詳細地說明這些選項和參考組件。The linked articles explain these options and reference assemblies in more detail.

公用或開放原始碼簽署Public or Open Source signing

-publicsign 編譯器選項會指示編譯器使用公開金鑰簽署組件。The -publicsign compiler option instructs the compiler to sign the assembly using a public key. 該組件標示為已簽署,但簽章取自公開金鑰。The assembly is marked as signed, but the signature is taken from the public key. 此選項可讓您使用公開金鑰,從開放原始碼專案建置簽署的組件。This option enables you to build signed assemblies from open-source projects using a public key.

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

pathmappathmap

-pathmap 編譯器選項會指示編譯器使用對應的來源路徑取代建置環境的來源路徑。The -pathmap compiler option instructs the compiler to replace source paths from the build environment with mapped source paths. -pathmap 選項控制編譯器寫入 PDB 檔案或 CallerFilePathAttribute 的來源路徑。The -pathmap option controls the source path written by the compiler to PDB files or for the CallerFilePathAttribute.

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