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:

.Net Core 3.x 和 .NET Standard 2.1支援 c # 8.0。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屬性不會變更狀態,因此您可以藉由將修飾詞加入至宣告來修正此警告 readonlyThe 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 存取子不會修改狀態,您必須 readonly 明確宣告。The compiler doesn't assume get accessors don't modify state; you must declare readonly explicitly. 自動實作為屬性的例外狀況;編譯器會將所有自動執行的 getter 視為 readonly ,因此,您不需要將修飾詞加入 readonlyXY 屬性。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. 除非您移除修飾詞,否則無法編譯下列方法 readonlyThe 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 實例成員一節。For more information, see the readonly instance members section of the Structure types article.

預設介面方法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. 預設介面方法也會啟用類似于「特性」語言功能的案例。Default interface methods also enable scenarios similar to a "traits" language feature.

預設介面方法會影響許多案例和語言元素。Default interface methods affect 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 switchC# 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 在它的每個區塊中產生一個值 caseOften, 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. 該計算不是類別的核心責任 AddressThat 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.075M,
        { 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. 如果您未涵蓋 switch 運算式中的所有可能案例,編譯器會為您產生警告。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(). 如果語句中的運算式無法處置,編譯器會產生錯誤 usingThe 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「靜態區域函式不可包含對 <variable> 的參考。」。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 structThis 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.

非同步可處置Asynchronous disposable

從 c # 8.0 開始,語言支援可執行介面的非同步可處置類型 System.IAsyncDisposableStarting with C# 8.0, the language supports asynchronous disposable types that implement the System.IAsyncDisposable interface. 運算式的運算元 using 可以執行 IDisposableIAsyncDisposableThe operand of a using expression can implement either IDisposable or IAsyncDisposable. 在案例中,編譯器會將程式 IAsyncDisposable 代碼產生至 awaitTask 傳回的 IAsyncDisposable.DisposeAsyncIn the case of IAsyncDisposable, the compiler generates code to await the Task returned from IAsyncDisposable.DisposeAsync. 如需詳細資訊,請參閱 using 語句For more information, see the using statement.

索引和範圍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.
  • End 運算子的索引 ^ ,指定索引相對於序列結尾。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 can also 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 聯合指派Null-coalescing assignment

C # 8.0 引進了 null 聯合指派運算子 ??=C# 8.0 introduces the null-coalescing assignment operator ??=. ??=只有當左運算元評估為時,您才可以使用運算子,將其右運算元的值指派給其左邊的運算元 nullYou 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

$ @逐字字串中的和 token 順序可以是 any: $@"..."@$"..." 都是有效的內插逐字字串。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.