C# 7.0 的新功能What's new in C# 7.0

C# 7.0 新增許多新功能至 C# 語言:C# 7.0 adds a number of new features to the C# language:

  • out 變數out variables
    • 您可以宣告 out 內嵌值作為使用它們之方法的引數。You can declare out values inline as arguments to the method where they're used.
  • 元組Tuples
    • 您可以建立包含多個公用欄位的輕量、未具名的類型。You can create lightweight, unnamed types that contain multiple public fields. 編譯器和 IDE 工具了解這些類型的語意。Compilers and IDE tools understand the semantics of these types.
  • 捨棄Discards
    • 捨棄是當您不在意指派的值時,於指派內使用的僅限寫入且暫時之變數。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.
  • ref 區域變數和傳回ref locals and returns
    • 方法區域變數和傳回值可以是其他儲存體的參考。Method local variables and return values can be references to other storage.
  • 區域函式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.
  • 通用的非同步傳回型別Generalized async return types
    • 使用 async 修飾詞宣告的方法除了 TaskTask<T> 之外,也能傳回其他類型。Methods declared with the async modifier can return other types in addition to Task and Task<T>.
  • 數值常值語法增強功能Numeric literal syntax improvements
    • 新的語彙基元改善了數值常數的可讀性。New tokens improve readability for numeric constants.

此文章的其餘部分將概述各個功能。The remainder of this article provides an overview of each feature. 您將了解每項功能背後的原因。For each feature, you'll learn the reasoning behind it. 您將了解語法。You'll learn 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.

out 變數out variables

在這個版本中,支援 out 參數的現有語法已經過改善。The existing syntax that supports out parameters has been improved in this version. 您現在可以在方法呼叫的引數清單中宣告 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 above. 不過,語言有支援使用隱含型別區域變數︰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 another line above.
  • 不需要指派初始值。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.

TupleTuples

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

注意

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. 這些名稱只會在編譯時間存在而不會保留 (例如,在執行階段使用反映調查 Tuple 時)。Those names exist only at compile time and aren't preserved, for example when inspecting the tuple using reflection at runtime.

在 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;

您可以在 Tuple 文章中深入了解 Tuple。You can learn more in depth about tuples in the tuples article.

捨棄Discards

通常在您解構元組或以 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 feature that allows you to implement method dispatch on properties other than the type of an object. 您可能已經熟悉根據物件類型的方法分派。You're probably already familiar with method dispatch based on the type of an object. 在物件導向程式設計中,虛擬和覆寫方法可提供語言語法,以根據物件的類型實作方法分派。In object-oriented programming, virtual and override methods provide language syntax to implement method dispatching based on an object's type. 基底和衍生類別能提供不同的實作。Base and Derived classes provide different implementations. 模式比對運算式會擴充這個概念,讓您可以輕鬆地為不是透過繼承階層架構而關聯的類型和資料元素實作類似的分派模式。Pattern matching expressions extend this concept so that you can easily implement similar dispatch patterns for types and data elements that aren't related through an inheritance hierarchy.

模式比對支援 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# 中的模式比對中深入了解模式比對。You can learn more about pattern matching in Pattern Matching in C#.

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 關鍵字一文。For more information, see the ref keyword article.

區域函式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.";
    }
}

注意

區域函式支援的部分設計也可以使用「Lambda 運算式」 完成。Some of the designs that are supported by local functions could also be accomplished using lambda expressions. 有興趣的人可以進一步了解差異Those interested can read more about the differences

更多運算式主體成員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 引進了 throw 運算式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.

通用的非同步傳回型別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 Framework,才便利用這項新的語言功能︰As one concrete example, the ValueTask type has been added to the .NET framework 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.

數值常值的語法增強功能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 above in the binary constant. 千位分隔符號可以出現在常數的任何位置。The digit separator can appear anywhere in the constant. 對於以 10 為底的數字,通常會使用它作為千位分隔符號︰For base 10 numbers, it is common to use it as a thousands separator:

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.