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

C# 8.0 向 C# 語言添加了以下功能和增強功能:C# 8.0 adds the following features and enhancements to the C# language:

C# 8.0 在 .NET Core 3.x.NET 標準 2.1上受支援。C# 8.0 is supported on .NET Core 3.x and .NET Standard 2.1. 有關詳細資訊,請參閱C# 語言版本控制For more information, see C# language versioning.

本文的其餘部分會簡短說明這些功能。The remainder of this article briefly describes these features. 提供教學課程及概觀的連結,其中包含深入詳盡的文章。Where in-depth articles are available, links to those tutorials and overviews are provided. 您可以使用 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 存放庫的 csharp8 子目錄。Set the current directory to the csharp8 subdirectory for the try-samples repository.
  4. 執行 dotnet tryRun dotnet try.

唯讀成員Readonly members

您可以將readonly修改器應用於結構的成員。You can apply the readonly modifier to members of a struct. 它指示成員不修改狀態。It indicates that the member doesn't modify state. 它比套用 readonly 修飾詞到 struct 宣告更為精細。It's more granular than applying the readonly modifier to a struct declaration. 假設您有下列可變動結構:Consider the following mutable struct:

public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Distance => Math.Sqrt(X * X + Y * Y);

    public override string ToString() =>
        $"({X}, {Y}) is {Distance} from the origin";
}

與大多數結構一樣,ToString()該方法不會修改狀態。Like most structs, the ToString() method doesn't modify state. 您可以透過新增 readonly 修飾詞到 ToString() 的宣告來指出此情況:You could indicate that by adding the readonly modifier to the declaration of ToString():

public readonly override string ToString() =>
    $"({X}, {Y}) is {Distance} from the origin";

前面的更改將生成編譯器警告,因為ToString訪問Distance屬性,該屬性未標記為readonlyThe preceding change generates a compiler warning, because ToString accesses the Distance property, which isn't marked readonly:

warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'

當它需要建立防禦性複本時,編譯器會警告您。The compiler warns you when it needs to create a defensive copy. 屬性Distance不會更改狀態,因此可以通過將readonly修改器添加到聲明來修復此警告:The Distance property doesn't change state, so you can fix this warning by adding the readonly modifier to the declaration:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

請注意,readonly在唯讀屬性上需要修改器。Notice that the readonly modifier is necessary on a read-only property. 編譯器不假定get訪問器不修改狀態,但您必須顯式聲明readonlyThe compiler doesn't assume get accessors don't modify state; you must declare readonly explicitly. 自動實現的屬性是一個例外;編譯器將所有自動實現的 getter 視為唯讀,因此此處無需將readonly修飾符添加到 和X``Y屬性。Auto-implemented properties are an exception; the compiler will treat all auto-implemented getters as readonly, so here there's no need to add the readonly modifier to the X and Y properties.

編譯器強制實施readonly成員不修改狀態的規則。The compiler does enforce the rule that readonly members don't modify state. 除非刪除修改器,readonly否則以下方法不會編譯:The following method won't compile unless you remove the readonly modifier:

public readonly void Translate(int xOffset, int yOffset)
{
    X += xOffset;
    Y += yOffset;
}

此功能可讓您指定您的設計意圖,以便編譯器可以強制套用,並根據該意圖進行最佳化。This feature lets you specify your design intent so the compiler can enforce it, and make optimizations based on that intent. 您可以在 上readonly的語言參考文章中瞭解有關唯讀成員的更多內容。You can learn more about readonly members in the language reference article on readonly.

預設介面方法Default interface methods

您現在可以新增成員到介面並提供那些成員的實作。You can now add members to interfaces and provide an implementation for those members. 此語言功能可讓 API 作者在較新的版本中新增方法到介面中,而不會因為該介面的現有實作造成原始程式碼或二進位檔案相容性受影響。This language feature enables API authors to add methods to an interface in later versions without breaking source or binary compatibility with existing implementations of that interface. 現有實作會「繼承」** 預設實作。Existing implementations inherit the default implementation. 此功能也會讓 C# 與以 Android 或 Swift 為目標的 API 相互操作,這些 API 支援類似的功能。This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. 預設介面方法還啟用類似于"traits"語言功能的方案。Default interface methods also enable scenarios similar to a "traits" language feature.

預設介面方法會影響許多方案和語言元素。Default interface methods affects many scenarios and language elements. 我們的第一個教學課程涵蓋使用預設實作更新介面Our first tutorial covers updating an interface with default implementations. 其他教學課程與參考更新即將在正式發行時推出。Other tutorials and reference updates are coming in time for general release.

在更多位置使用更多的模式More patterns in more places

模式比對讓您能夠使用提供相關但不同種類資料間圖形相依功能的工具。Pattern matching gives tools to provide shape-dependent functionality across related but different kinds of data. C# 7.0 使用is運算式和 語句引入了類型模式和常量switch模式的語法。C# 7.0 introduced syntax for type patterns and constant patterns by using the is expression and the switch statement. 這些功能代表達成支援程式設計範例 (資料及功能分開) 的暫訂步驟。These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. 隨著業界趨勢轉向微服務及其他雲端式架構,也產生其他語言工具的需求。As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed.

C# 8.0 可展開此詞彙,讓您能夠在程式碼中的更多位置,使用更多的模式運算式。C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. 當您的資料與功能分開時,請考慮使用這些功能。Consider these features when your data and functionality are separate. 當您的演算法取決於物件執行階段類型外的事實時,請考慮使用模式比對。Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. 這些技術提供表達設計的另一種方式。These techniques provide another way to express designs.

除了在新位置的新模式之外,C# 8.0 還新增了遞迴模式In addition to new patterns in new places, C# 8.0 adds recursive patterns. 任何模式運算式的結果皆是運算式。The result of any pattern expression is an expression. 遞迴模式僅為套用到其他模式運算式輸出的模式運算式。A recursive pattern is simply a pattern expression applied to the output of another pattern expression.

switch 運算式Switch expressions

通常,語句switch在其case每個塊中生成值。Often, a switch statement produces a value in each of its case blocks. Switch 運算式讓您能夠使用更精簡的運算式語法。Switch expressions enable you to use more concise expression syntax. 重複的 casebreak 關鍵字和大括號都減少了。There are fewer repetitive case and break keywords, and fewer curly braces. 例如,請考慮以下列出彩虹色彩的列舉:As an example, consider the following enum that lists the colors of the rainbow:

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}

如果您的應用程式定義了由 RGB 元件所建構的 RGBColor 型別,則可以使用包含 switch 運算式的下列方法,將 Rainbow 值轉換為其 RGB 值:If your application defined an RGBColor type that is constructed from the R, G and B components, you could convert a Rainbow value to its RGB values using the following method containing a switch expression:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

幾項語法改進如下:There are several syntax improvements here:

  • 變數挪到了 switch 關鍵字前。The variable comes before the switch keyword. 不同的順序讓您可輕鬆區分出 switch 運算式及 switch 陳述式。The different order makes it visually easy to distinguish the switch expression from the switch statement.
  • case: 元素取代為 =>The case and : elements are replaced with =>. 這更加精簡而且符合直覺。It's more concise and intuitive.
  • default 案例取代為 _ 捨棄。The default case is replaced with a _ discard.
  • 主體為運算式,而非陳述式。The bodies are expressions, not statements.

對比該語法與使用傳統 switch 陳述式的對等程式碼:Contrast that with the equivalent code using the classic switch statement:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}

屬性模式Property patterns

屬性模式使您得以比對所檢查物件的屬性。The property pattern enables you to match on properties of the object examined. 試想必須根據購買者地址計算營業稅的電子商務網站。Consider an eCommerce site that must compute sales tax based on the buyer's address. 這種計算不是Address類的核心責任。That computation isn't a core responsibility of an Address class. 這項計算會隨著時間變更,而且可能比地址格式的變更更加頻繁。It will change over time, likely more often than address format changes. 營業稅額取決於地址的 State 屬性。The amount of sales tax depends on the State property of the address. 下列方法會使用屬性模式,從地址及價格計算營業稅:The following method uses the property pattern to compute the sales tax from the address and the price:

public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.75M,
        { State: "MI" } => salePrice * 0.05M,
        // other cases removed for brevity...
        _ => 0M
    };

模式比對會建立簡潔的語法以表示此演算法。Pattern matching creates a concise syntax for expressing this algorithm.

Tuple 模式Tuple patterns

某些演算法需仰賴數個輸入。Some algorithms depend on multiple inputs. Tuple 模式可讓您根據以 tuple 形式表示的多個值進行切換。Tuple patterns allow you to switch based on multiple values expressed as a tuple. 下列範例示範「剪刀、石頭、布」** 遊戲的 switch 運算式:The following code shows a switch expression for the game rock, paper, scissors:

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };

訊息會指出優勝者。The messages indicate the winner. 捨棄案例代表平手的三個組合,或其他文字輸入。The discard case represents the three combinations for ties, or other text inputs.

位置模式Positional patterns

某些類型會包含 Deconstruct 方法,其能將自己的屬性解構成離散變數。Some types include a Deconstruct method that deconstructs its properties into discrete variables. 可存取 Deconstruct 方法時,您可以使用位置模式來檢查物件的屬性,然後將那些屬性用於某個模式。When a Deconstruct method is accessible, you can use positional patterns to inspect properties of the object and use those properties for a pattern. 請考慮下列 Point 類別,其包含 Deconstruct 方法來針對 XY 建立離散變數:Consider the following Point class that includes a Deconstruct method to create discrete variables for X and Y:

public class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

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

此外,請考慮下列代表象限各種不同位置的列舉:Additionally, consider the following enum that represents various positions of a quadrant:

public enum Quadrant
{
    Unknown,
    Origin,
    One,
    Two,
    Three,
    Four,
    OnBorder
}

下列方法會使用位置模式擷取 xy 的值。The following method uses the positional pattern to extract the values of x and y. 接著,它使用 when 子句判斷點的 QuadrantThen, it uses a when clause to determine the Quadrant of the point:

static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

xy 為 0 (非兩者) 時,上述 switch 中的捨棄模式就會符合。The discard pattern in the preceding switch matches when either x or y is 0, but not both. switch 運算式必須產生值或者擲回例外狀況。A switch expression must either produce a value or throw an exception. 若沒有任何案例符合,則 switch 運算式會擲回例外狀況。If none of the cases match, the switch expression throws an exception. 如果未涵蓋交換器運算式中的所有可能情況,編譯器將為您生成警告。The compiler generates a warning for you if you don't cover all possible cases in your switch expression.

您可以在此模式比對進階教學課程中探索模式比對技巧。You can explore pattern matching techniques in this advanced tutorial on pattern matching.

using 宣告Using declarations

using 宣告為以 using 關鍵字開頭的變數宣告。A using declaration is a variable declaration preceded by the using keyword. 其會告知編譯器,應在括住的範圍結尾處置所宣告的變數。It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope. 例如,試想下列寫入文字檔的程式碼:For example, consider the following code that writes a text file:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    // Notice how we declare skippedLines after the using statement.
    int skippedLines = 0;
    foreach (string line in lines)
    {
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
        else
        {
            skippedLines++;
        }
    }
    // Notice how skippedLines is in scope here.
    return skippedLines;
    // file is disposed here
}

在上述範例中,到達方法的右大括號時,就會處置檔案。In the preceding example, the file is disposed when the closing brace for the method is reached. 這是宣告 file 之範圍的結尾。That's the end of the scope in which file is declared. 上述程式碼相當於使用傳統 using 陳述式的下列程式碼:The preceding code is equivalent to the following code that uses the classic using statement:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    // We must declare the variable outside of the using block
    // so that it is in scope to be returned.
    int skippedLines = 0;
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
            else
            {
                skippedLines++;
            }
        }
    } // file is disposed here
    return skippedLines;
}

在上述範例中,到達與 using 陳述式相關的結尾右大括號時,就會處置檔案。In the preceding example, the file is disposed when the closing brace associated with the using statement is reached.

在這兩個案例中,編譯器皆會產生對 Dispose() 的呼叫。In both cases, the compiler generates the call to Dispose(). 如果語句中的運算式不是一次性的,using編譯器將建置錯誤。The compiler generates an error if the expression in the using statement isn't disposable.

靜態區域函式Static local functions

您現可將 static 修飾詞新增至區域函式,以確保區域函式不會從括住的範圍擷取 (參考) 任何變數。You can now add the static modifier to local functions to ensure that local function doesn't capture (reference) any variables from the enclosing scope. 這樣會產生 CS8421「靜態區域函式不可包含對 <變數> 的參考」。Doing so generates CS8421, "A static local function can't contain a reference to <variable>."

請考慮下列程式碼:Consider the following code. 區域函式 LocalFunction 會存取在所括住範圍 (方法 M) 中宣告的變數 yThe local function LocalFunction accesses the variable y, declared in the enclosing scope (the method M). 因此,無法使用 static 修飾詞宣告 LocalFunctionTherefore, LocalFunction can't be declared with the static modifier:

int M()
{
    int y;
    LocalFunction();
    return y;

    void LocalFunction() => y = 0;
}

下列程式碼包含靜態區域函式。The following code contains a static local function. 因為其不會存取所括住範圍中的任何變數,所以可以為靜態:It can be static because it doesn't access any variables in the enclosing scope:

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

可處置的 ref structDisposable ref structs

使用struct``ref修飾符聲明的不能實現任何介面,因此無法實現IDisposableA struct declared with the ref modifier may not implement any interfaces and so can't implement IDisposable. 因此,若要讓 ref struct 可處置,其必須具有可存取的 void Dispose() 方法。Therefore, to enable a ref struct to be disposed, it must have an accessible void Dispose() method. 此功能也適用于readonly ref struct聲明。This feature also applies to readonly ref struct declarations.

可為 Null 的參考型別Nullable reference types

在可為 Null 的註解內容中,參考型別的任何變數皆視為不可為 Null 的參考型別Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. 若您要表示變數可能為 Null,則必須使用 ? 來附加類型名稱,以將變數宣告為可為 Null 的參考型別If you want to indicate that a variable may be null, you must append the type name with the ? to declare the variable as a nullable reference type.

對於不可為 Null 的參考型別,編譯器會使用流程分析來確保將區域變數在宣告時,初始化為非 Null 的值。For nonnullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to a non-null value when declared. 必須在建構期間將欄位初始化。Fields must be initialized during construction. 如果變數不是通過調用任何可用的建構函式或初始化器設置,編譯器將生成警告。The compiler generates a warning if the variable isn't set by a call to any of the available constructors or by an initializer. 此外,也不能對不可為 Null 的參考型別指派可為 Null 的值。Furthermore, nonnullable reference types can't be assigned a value that could be null.

系統不會檢查可為 Null 的參考型別,來確保其不會指派或初始化為 Null。Nullable reference types aren't checked to ensure they aren't assigned or initialized to null. 不過,編譯器會在將變數存取或指派至不可為 Null 的參考型別之前,使用流程分析來確保已對可為 Null 參考型別的所有變數檢查可 Null 性。However, the compiler uses flow analysis to ensure that any variable of a nullable reference type is checked against null before it's accessed or assigned to a nonnullable reference type.

您可在可為 Null 的參考型別概觀中深入了解功能。You can learn more about the feature in the overview of nullable reference types. 在此可為 Null 的參考型別教學課程中,親自在新的應用程式內試用。Try it yourself in a new application in this nullable reference types tutorial. 移轉應用程式以使用可為 Null 的參考型別教學課程中,了解移轉現有程式碼基底的步驟,進而利用可為 Null 的參考型別。Learn about the steps to migrate an existing codebase to make use of nullable reference types in the migrating an application to use nullable reference types tutorial.

非同步資料流Asynchronous streams

自 C# 8.0 起,您可非同步地建立及取用資料流。Starting with C# 8.0, you can create and consume streams asynchronously. 傳回非同步資料流的方法具有三個屬性:A method that returns an asynchronous stream has three properties:

  1. 使用 async 修飾詞來宣告。It's declared with the async modifier.
  2. 其會傳回 IAsyncEnumerable<T>It returns an IAsyncEnumerable<T>.
  3. 方法會包含 yield return 陳述式以傳回非同步資料流中的後續元素。The method contains yield return statements to return successive elements in the asynchronous stream.

當您列舉資料流的元素時,必須在 foreach 關鍵字前新增 await 關鍵字,才可取用非同步資料流。Consuming an asynchronous stream requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream. 需要方法才可新增 await 關鍵字,而且該方法列舉要使用 async 修飾詞宣告的非同步資料流,並傳回 async 方法允許的類型。Adding the await keyword requires the method that enumerates the asynchronous stream to be declared with the async modifier and to return a type allowed for an async method. 通常這代表傳回 TaskTask<TResult>Typically that means returning a Task or Task<TResult>. 也可以是 ValueTaskValueTask<TResult>It can also be a ValueTask or ValueTask<TResult>. 方法可同時取用及產生非同步資料流,這代表其會傳回 IAsyncEnumerable<T>A method can both consume and produce an asynchronous stream, which means it would return an IAsyncEnumerable<T>. 下列程式碼會產生 0 到 19 的序列,產生每個號碼需等待 100 毫秒的間隔:The following code generates a sequence from 0 to 19, waiting 100 ms between generating each number:

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

您應使用 await foreach 陳述式列舉序列:You would enumerate the sequence using the await foreach statement:

await foreach (var number in GenerateSequence())
{
    Console.WriteLine(number);
}

您可在我們有關建立及取用非同步資料流的教學課程中,親自試用非同步資料流。You can try asynchronous streams yourself in our tutorial on creating and consuming async streams. 預設情況元素在捕獲的上下文中處理。By default, stream elements are processed in the captured context. 如果要禁用上下文捕獲,請使用TaskAsyncEnumerableExtensions.ConfigureAwait擴充方法。If you want to disable capturing of the context, use the TaskAsyncEnumerableExtensions.ConfigureAwait extension method. 有關同步上下文和捕獲當前上下文的詳細資訊,請參閱有關使用基於任務的非同步模式的文章。For more information about synchronization contexts and capturing the current context, see the article on consuming the Task-based asynchronous pattern.

索引和範圍Indices and ranges

索引和範圍提供了簡潔的語法,用於按循序存取單個元素或範圍。Indices and ranges provide a succinct syntax for accessing single elements or ranges in a sequence.

此語言支援依賴于兩種新類型和兩個新運算子:This language support relies on two new types, and two new operators:

  • System.Index 代表序列的索引。System.Index represents an index into a sequence.
  • 來自結束運算子^的索引 ,它指定索引相對於序列的末尾。The index from end operator ^, which specifies that an index is relative to the end of the sequence.
  • System.Range 代表序列的子範圍。System.Range represents a sub range of a sequence.
  • 範圍運算子.., 指定範圍的開始和結束作為其運算元。The range operator .., which specifies the start and end of a range as its operands.

讓我們從索引的規則開始。Let's start with the rules for indexes. 假設有一個陣列 sequenceConsider an array sequence. 0 索引與 sequence[0] 相同。The 0 index is the same as sequence[0]. ^0 索引與 sequence[sequence.Length] 相同。The ^0 index is the same as sequence[sequence.Length]. 請注意,sequence[^0] 會擲回例外狀況,就樣 sequence[sequence.Length] 會這樣做一樣。Note that sequence[^0] does throw an exception, just as sequence[sequence.Length] does. 針對任何數字 n,索引 ^nsequence.Length - n 相同。For any number n, the index ^n is the same as sequence.Length - n.

指定範圍「開頭」** 與「結尾」** 的範圍。A range specifies the start and end of a range. 範圍的開頭是包羅萬象的,但範圍的結束是獨佔的,這意味著起始項包含在範圍內,但範圍中不包括結束The start of the range is inclusive, but the end of the range is exclusive, meaning the start is included in the range but the end isn't included in the range. 範圍 [0..^0] 代整個範圍,就像 [0..sequence.Length] 代表整個範圍一樣。The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

讓我們來看以下幾個範例。Let's look at a few examples. 試想下列使用其索引從開頭或從結尾標註的陣列:Consider the following array, annotated with its index from the start and from the end:

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

您可使用 ^1 索引擷取最後一個字組:You can retrieve the last word with the ^1 index:

Console.WriteLine($"The last word is {words[^1]}");
// writes "dog"

下列程式碼會建立具有 "quick"、"brown" 和 "fox" 字組的子範圍。The following code creates a subrange with the words "quick", "brown", and "fox". 其會包含 words[1]words[3]It includes words[1] through words[3]. 項目 words[4] 不在範圍內。The element words[4] isn't in the range.

var quickBrownFox = words[1..4];

下列程式碼會建立具有 "lazy" 和 "dog" 的子範圍。The following code creates a subrange with "lazy" and "dog". 其包含 words[^2]words[^1]It includes words[^2] and words[^1]. 不包括最終索引words[^0]The end index words[^0] isn't included:

var lazyDog = words[^2..^0];

下列範例會建立不限定開始、結束或兩者的範圍:The following examples create ranges that are open ended for the start, end, or both:

var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"

您也可將範圍宣告為變數:You can also declare ranges as variables:

Range phrase = 1..4;

接著範圍可用於 [] 字元中:The range can then be used inside the [ and ] characters:

var text = words[phrase];

不僅陣列支援索引和範圍。Not only arrays support indices and ranges. 您還可以使用索引和範圍與字串Span<T>ReadOnlySpan<T>You also can use indices and ranges with string, Span<T>, or ReadOnlySpan<T>. 有關詳細資訊,請參閱索引和範圍的類型支援For more information, see Type support for indices and ranges.

您可以在索引及範圍上的教學課程中探索更多關於索引及範圍的資訊。You can explore more about indices and ranges in the tutorial on indices and ranges.

空合併分配Null-coalescing assignment

C# 8.0 引入了空聚聚指派運算子??=C# 8.0 introduces the null-coalescing assignment operator ??=. 僅當左側運算元??=計算到null時,才能使用運算子將其右側運算元的值分配給其左側運算元。You can use the ??= operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null.

List<int> numbers = null;
int? i = null;

numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);

Console.WriteLine(string.Join(" ", numbers));  // output: 17 17
Console.WriteLine(i);  // output: 17

有關詳細資訊,請參閱?和??• 運算子文章。For more information, see the ?? and ??= operators article.

非託管構造類型Unmanaged constructed types

在 C# 7.3 和更早版本中,構造類型(至少包含一個類型參數的類型)不能為非託管類型In C# 7.3 and earlier, a constructed type (a type that includes at least one type argument) can't be an unmanaged type. 從 C# 8.0 開始,如果構造的數值型別僅包含非託管類型的欄位,則該數值型別將處於非託管類型。Starting with C# 8.0, a constructed value type is unmanaged if it contains fields of unmanaged types only.

例如,給定泛型Coords<T>類型的以下定義:For example, given the following definition of the generic Coords<T> type:

public struct Coords<T>
{
    public T X;
    public T Y;
}

類型Coords<int>是 C# 8.0 及更高版本中的非託管類型。the Coords<int> type is an unmanaged type in C# 8.0 and later. 與任何非託管類型一樣,您可以創建指向此類型的變數的指標,或在堆疊上為此類實例分配區塊Like for any unmanaged type, you can create a pointer to a variable of this type or allocate a block of memory on the stack for instances of this type:

Span<Coords<int>> coordinates = stackalloc[]
{
    new Coords<int> { X = 0, Y = 0 },
    new Coords<int> { X = 0, Y = 3 },
    new Coords<int> { X = 4, Y = 0 }
};

有關詳細資訊,請參閱非託管類型For more information, see Unmanaged types.

嵌套運算式中的 StackallocStackalloc in nested expressions

從 C# 8.0 開始,如果stackalloc運算式的結果為System.Span<T>System.ReadOnlySpan<T>或 類型,則可以在其他stackalloc運算式中使用運算式:Starting with C# 8.0, if the result of a stackalloc expression is of the System.Span<T> or System.ReadOnlySpan<T> type, you can use the stackalloc expression in other expressions:

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6 ,8 });
Console.WriteLine(ind);  // output: 1

增強插值逐字字串Enhancement of interpolated verbatim strings

插值逐$@字串中的interpolated和 標記的順序可以是任意:兩$@"..."``@$"..."者都是有效插值逐字字串。Order of the $ and @ tokens in interpolated verbatim strings can be any: both $@"..." and @$"..." are valid interpolated verbatim strings. 在早期的 C# 版本中$,權杖必須出現在權杖@之前。In earlier C# versions, the $ token must appear before the @ token.