Neues in C# 8.0What's new in C# 8.0

C# 8.0 fügt der Sprache C# die folgenden Features und Verbesserungen hinzu:C# 8.0 adds the following features and enhancements to the C# language:

C# 8.0 wird unter .NET Core 3.x und .NET Standard 2.1 unterstützt.C# 8.0 is supported on .NET Core 3.x and .NET Standard 2.1. Weitere Informationen finden Sie unter C#-Sprachversionsverwaltung.For more information, see C# language versioning.

Der Rest dieses Artikels beschreibt diese Funktionen kurz.The remainder of this article briefly describes these features. Wenn ausführliche Artikel verfügbar sind, werden Links zu diesen Tutorials und Übersichten bereitgestellt.Where in-depth articles are available, links to those tutorials and overviews are provided. Sie können sich diese Funktionen in unserer Umgebung mit dem globalen dotnet try-Tool näher ansehen:You can explore these features in your environment using the dotnet try global tool:

  1. Installieren Sie das globale dotnet-try-Tool.Install the dotnet-try global tool.
  2. Klonen Sie das dotnet/try-samples-Repository.Clone the dotnet/try-samples repository.
  3. Legen Sie das aktuelle Verzeichnis auf das Unterverzeichnis csharp8 für das try-samples-Repository fest.Set the current directory to the csharp8 subdirectory for the try-samples repository.
  4. Führen Sie aus dotnet try.Run dotnet try.

Readonly-MemberReadonly members

Sie können den readonly-Modifizierer auf jeden Member einer Struktur anwenden.You can apply the readonly modifier to members of a struct. Damit wird angezeigt, dass der Member den Zustand nicht ändert.It indicates that the member doesn't modify state. Dies ist granularer als das Anwenden des readonly-Modifikators auf eine struct-Deklaration.It's more granular than applying the readonly modifier to a struct declaration. Betrachten Sie folgende veränderliche Struktur: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";
}

Wie bei den meisten Strukturen verändert die ToString()-Methode den Zustand nicht.Like most structs, the ToString() method doesn't modify state. Sie könnten dies durch Hinzufügen des readonly-Modifikators zur Deklaration von ToString() angeben: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";

Die vorhergehende Änderung generiert eine Compilerwarnung, weil ToString auf die Distance-Eigenschaft zugreift, die nicht als readonly markiert ist:The 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'

Der Compiler warnt Sie, wenn er eine Defensivkopie erstellen muss.The compiler warns you when it needs to create a defensive copy. Die Distance-Eigenschaft verändert nicht den Zustand, sodass Sie diese Warnung aufheben können, indem Sie der Deklaration den readonly-Modifizierer hinzufügen: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);

Beachten Sie, dass der readonly-Modifizierer bei einer schreibgeschützten Eigenschaft erforderlich ist.Notice that the readonly modifier is necessary on a read-only property. Der Compiler geht nicht davon aus, dass get-Zugriffsmethoden den Zustand nicht ändern. Sie müssen readonly explizit deklarieren.The compiler doesn't assume get accessors don't modify state; you must declare readonly explicitly. Automatisch implementierte Eigenschaften sind eine Ausnahme; der Compiler sieht alle automatisch implementierten Getter als schreibgeschützt an, sodass es hier nicht notwendig ist, den readonly-Modifizierer zu den X- und Y-Eigenschaften hinzuzufügen.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.

Der Compiler erzwingt die Regel, dass readonly-Member den Status nicht ändern.The compiler does enforce the rule that readonly members don't modify state. Die folgende Methode wird nicht kompiliert, es sei denn, Sie entfernen den readonly-Modifizierer:The following method won't compile unless you remove the readonly modifier:

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

Mit diesem Feature können Sie Ihre Designabsicht angeben, damit der Compiler sie erzwingen und Optimierungen basierend auf dieser Absicht vornehmen kann.This feature lets you specify your design intent so the compiler can enforce it, and make optimizations based on that intent. Erfahren Sie mehr über schreibgeschützte Member in dem Artikel readonly.You can learn more about readonly members in the language reference article on readonly.

StandardschnittstellenmethodenDefault interface methods

Sie können nun Member zu Schnittstellen hinzufügen und eine Implementierung für diese Member bereitstellen.You can now add members to interfaces and provide an implementation for those members. Dieses Sprachfeature ermöglicht es API-Autoren, in späteren Versionen Methoden zu einer Schnittstelle hinzuzufügen, ohne die Quell- oder Binärkompatibilität mit bestehenden Implementierungen dieser Schnittstelle zu beeinträchtigen.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. Bestehende Implementierungen erben die Standardimplementierung.Existing implementations inherit the default implementation. Dieses Feature ermöglicht zudem die Interaktion zwischen C# und APIs, die auf Android oder Swift abzielen und ähnliche Funktionen unterstützen.This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. Standardschnittstellenmethoden ermöglichen auch Szenarien, die einem „Traits“-Sprachfeature ähneln.Default interface methods also enable scenarios similar to a "traits" language feature.

Standardschnittstellenmethoden wirken sich auf viele Szenarien und Sprachelemente aus.Default interface methods affects many scenarios and language elements. Unser erstes Tutorial behandelt die Aktualisierung einer Schnittstelle mit Standardimplementierungen.Our first tutorial covers updating an interface with default implementations. Weitere Tutorials und Referenzaktualisierungen folgen rechtzeitig zur allgemeinen Veröffentlichung.Other tutorials and reference updates are coming in time for general release.

Weitere Muster an mehr OrtenMore patterns in more places

Musterabgleich bietet Tools zur Bereitstellung formabhängiger Funktionalität für verwandte, aber unterschiedliche Datentypen.Pattern matching gives tools to provide shape-dependent functionality across related but different kinds of data. C# 7.0 führte eine Syntax für Typmuster und konstante Muster ein, indem der Ausdruck is und die Anweisung switch verwendet wurden.C# 7.0 introduced syntax for type patterns and constant patterns by using the is expression and the switch statement. Diese Funktionen stellten die ersten vorläufigen Schritte zur Unterstützung von Programmierparadigmen dar, bei denen Daten und Funktionalität getrennt voneinander sind.These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. Da sich die Branche immer mehr auf Mikroservices und andere Cloud-basierte Architekturen konzentriert, werden andere Sprachtools benötigt.As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed.

C# 8.0 erweitert dieses Vokabular, sodass Sie mehr Musterausdrücke an mehreren Stellen in Ihrem Code verwenden können.C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. Berücksichtigen Sie diese Funktionen, wenn Ihre Daten und Funktionen getrennt sind.Consider these features when your data and functionality are separate. Berücksichtigen Sie den Musterabgleich, wenn Ihre Algorithmen von einer anderen Tatsache als dem Runtimetyp eines Objekts abhängen.Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. Diese Verfahren bieten eine weitere Möglichkeit, Entwürfe auszudrücken.These techniques provide another way to express designs.

Zusätzlich zu neuen Mustern an neuen Orten fügt C# 8.0 rekursive Muster hinzu.In addition to new patterns in new places, C# 8.0 adds recursive patterns. Das Ergebnis eines beliebigen Musterausdrucks ist ein Ausdruck.The result of any pattern expression is an expression. Ein rekursives Muster ist einfach ein Musterausdruck, der auf die Ausgabe eines anderen Musterausdrucks angewendet wird.A recursive pattern is simply a pattern expression applied to the output of another pattern expression.

Switch-AusdrückeSwitch expressions

Häufig liefert eine switch-Anweisung in jedem ihrer case-Blöcke einen Wert.Often, a switch statement produces a value in each of its case blocks. Mit switch-Ausdrücken können Sie eine präzisere Ausdruckssyntax verwenden.Switch expressions enable you to use more concise expression syntax. Es gibt weniger repetitive case- und break-Schlüsselwörter und weniger geschweifte Klammern.There are fewer repetitive case and break keywords, and fewer curly braces. Betrachten Sie als Beispiel die folgende Enumeration, die die Farben des Regenbogens enumeriert:As an example, consider the following enum that lists the colors of the rainbow:

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

Wenn Ihre Anwendung einen RGBColor-Typ definiert hat, der aus den Komponenten R, G und B aufgebaut ist, können Sie einen Rainbow-Wert in seine RGB-Werte konvertieren, indem Sie die folgende Methode verwenden, die einen Switch-Ausdruck enthält: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)),
    };

Es gibt hier verschiedene Syntaxverbesserungen:There are several syntax improvements here:

  • Die Variable steht vor dem switch-Schlüsselwort.The variable comes before the switch keyword. Die unterschiedliche Reihenfolge macht es optisch einfach, den switch-Ausdruck von der switch-Anweisung zu unterscheiden.The different order makes it visually easy to distinguish the switch expression from the switch statement.
  • Die case- und :-Elemente werden durch => ersetzt.The case and : elements are replaced with =>. Es ist präziser und intuitiver.It's more concise and intuitive.
  • Der default-Fall wird durch eine _-Ausschlussvariable ersetzt.The default case is replaced with a _ discard.
  • Die Textkörper sind Ausdrücke, keine Anweisungen.The bodies are expressions, not statements.

Stellen Sie dies mit dem entsprechenden Code unter Verwendung der klassischen switch-Anweisung gegenüber: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));
    };
}

EigenschaftsmusterProperty patterns

Mit dem Eigenschaftsmuster können Sie die Eigenschaften des untersuchten Objekts anpassen.The property pattern enables you to match on properties of the object examined. Denken Sie an eine eCommerce-Website, die die Umsatzsteuer auf der Grundlage der Adresse des Käufers berechnen muss.Consider an eCommerce site that must compute sales tax based on the buyer's address. Diese Berechnung ist keine Kernaufgabe einer Address-Klasse.That computation isn't a core responsibility of an Address class. Sie wird sich im Laufe der Zeit ändern, wahrscheinlich häufiger als Adressformatänderungen.It will change over time, likely more often than address format changes. Die Höhe der Umsatzsteuer hängt von der State-Eigenschaft der Adresse ab.The amount of sales tax depends on the State property of the address. Die folgende Methode verwendet das Eigenschaftsmuster, um die Umsatzsteuer aus der Adresse und dem Preis zu berechnen: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
    };

Ein Musterabgleich erstellt eine präzise Syntax,. um diesen Algorithmus auszudrücken.Pattern matching creates a concise syntax for expressing this algorithm.

TupelmusterTuple patterns

Einige Algorithmen sind von mehreren Eingaben abhängig.Some algorithms depend on multiple inputs. Tupelmuster erlauben Ihnen, auf Grundlage mehrerer Werte, ausgedrückt als ein Tupel, zu wechseln („switch“).Tuple patterns allow you to switch based on multiple values expressed as a tuple. Der folgende Code zeigt einen switch-Ausdruck für das Spiel Stein, Schere, Papier (Schnick, Schnack, Schnuck):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"
    };

Die Meldungen zeigen den Gewinner an.The messages indicate the winner. Der Fall „Verwerfen“ („case discard“) stellt die drei Kombinationen für Unentschieden oder andere Texteingaben dar.The discard case represents the three combinations for ties, or other text inputs.

PositionsmusterPositional patterns

Einige Typen umfassen eine Deconstruct-Methode, die ihre Eigenschaften in diskrete Variablen dekonstruiert.Some types include a Deconstruct method that deconstructs its properties into discrete variables. Wenn auf eine Deconstruct-Methode zugegriffen werden kann, können Sie Positionsmuster verwenden, um Eigenschaften des Objekts zu untersuchen, und diese Eigenschaften für ein Muster verwenden.When a Deconstruct method is accessible, you can use positional patterns to inspect properties of the object and use those properties for a pattern. Beachten Sie die folgende Point-Klasse, die eine Deconstruct-Methode umfasst, um diskrete Variablen für X und Y zu erstellen: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);
}

Beachten Sie zudem die folgende Enumeration, die verschiedene Positionen eines Quadranten darstellt:Additionally, consider the following enum that represents various positions of a quadrant:

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

Die folgende Methode verwendet das Positionsmuster, um die Werte von x und y zu extrahieren.The following method uses the positional pattern to extract the values of x and y. Dann wird mit einer when-Klausel der Quadrant des Punktes bestimmt:Then, 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
};

Das Ausschussmuster im vorherigen Switch stimmt überein, wenn entweder x oder y 0 ist, jedoch nicht beide.The discard pattern in the preceding switch matches when either x or y is 0, but not both. Ein switch-Ausdruck muss entweder einen Wert erzeugen oder eine Ausnahme auslösen.A switch expression must either produce a value or throw an exception. Wenn keiner der Fälle übereinstimmt, löst der switch-Ausdruck eine Ausnahme aus.If none of the cases match, the switch expression throws an exception. Der Compiler erzeugt für Sie eine Warnung, wenn Sie nicht alle möglichen Fälle in Ihrem Switch-Ausdruck abdecken.The compiler generates a warning for you if you don't cover all possible cases in your switch expression.

In diesem erweiterten Tutorial zum Musterabgleich erhalten Sie weitere Informationen zu Musterabgleichverfahren.You can explore pattern matching techniques in this advanced tutorial on pattern matching.

Using-DeklarationenUsing declarations

Eine using-Deklaration ist eine Variablendeklaration, der das Schlüsselwort using vorangestellt ist.A using declaration is a variable declaration preceded by the using keyword. Es teilt dem Compiler mit, dass die zu deklarierende Variable am Ende des umschließenden Bereichs angeordnet werden soll.It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope. Sehen Sie sich beispielsweise den folgenden Code an, der eine Textdatei schreibt: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
}

Im vorhergehenden Beispiel wird die Datei angeordnet, wenn die schließende Klammer für die Methode erreicht ist.In the preceding example, the file is disposed when the closing brace for the method is reached. Dies ist das Ende des Bereichs, in dem file deklariert wird.That's the end of the scope in which file is declared. Der obige Code entspricht dem folgenden Code mit klassischer using-Anweisung: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;
}

Im vorhergehenden Beispiel wird die Datei angeordnet, wenn die der using-Anweisung zugeordnete schließende Klammer erreicht ist.In the preceding example, the file is disposed when the closing brace associated with the using statement is reached.

In beiden Fällen generiert der Compiler den Aufruf von Dispose().In both cases, the compiler generates the call to Dispose(). Der Compiler erzeugt einen Fehler, wenn der Ausdruck in der using-Anweisung nicht verwerfbar ist.The compiler generates an error if the expression in the using statement isn't disposable.

Statische lokale FunktionenStatic local functions

Sie können nun den static-Modifikator zu lokalen Funktionen hinzufügen, um sicherzustellen, dass die lokale Funktion keine Variablen aus dem umschließenden Bereich erfasst (referenziert).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. Dadurch wird CS8421 erzeugt: „Eine statische lokale Funktion kann keine Referenz auf <variable> enthalten.“Doing so generates CS8421, "A static local function can't contain a reference to <variable>."

Betrachten Sie folgenden Code.Consider the following code. Die lokale Funktion LocalFunction greift auf die Variable y zu, die im umschließenden Bereich (die Methode M) deklariert ist.The local function LocalFunction accesses the variable y, declared in the enclosing scope (the method M). Daher kann LocalFunction nicht mit dem static-Modifikator deklariert werden:Therefore, LocalFunction can't be declared with the static modifier:

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

    void LocalFunction() => y = 0;
}

Der folgende Code enthält eine statische lokale Funktion.The following code contains a static local function. Er kann statisch sein, da er nicht auf Variablen im umschließenden Bereich zugreift: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;
}

Verwerfbare ReferenzstrukturenDisposable ref structs

Ein mit dem ref-Modifizierer deklarierter struct darf keine Schnittstellen und damit auch keine IDisposable implementieren.A struct declared with the ref modifier may not implement any interfaces and so can't implement IDisposable. Aus diesem Grund Aktivieren einer ref struct um verworfen werden, muss eine zugängliche void Dispose() Methode.Therefore, to enable a ref struct to be disposed, it must have an accessible void Dispose() method. Dieses Feature gilt auch für readonly ref struct-Deklarationen.This feature also applies to readonly ref struct declarations.

Nullwerte zulassende VerweistypenNullable reference types

In einem Nullwerte zulassenden Anmerkungskontext, wird jede Variable eines Referenztyps als keine Nullwerte zulassender Referenztyp betrachtet.Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. Wenn Sie angeben möchten, dass eine Variable Null sein kann, müssen Sie den Typnamen mit ? ergänzen, um die Variable als keine Nullwerte zulassenden Verweistyp zu deklarieren.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.

Wenn der Verweistyp keine Nullwerte zulässt, verwendet der Compiler die Flussanalyse, um sicherzustellen, dass lokale Variablen bei der Deklaration auf einen Nicht-Null-Wert initialisiert werden.For nonnullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to a non-null value when declared. Felder müssen während der Erstellung initialisiert werden.Fields must be initialized during construction. Der Compiler erzeugt eine Warnung, wenn die Variable nicht durch einen Aufruf eines der verfügbaren Konstruktoren oder durch einen Initialisierer festgelegt wird.The compiler generates a warning if the variable isn't set by a call to any of the available constructors or by an initializer. Darüber hinaus kann Verweistypen, die keine Nullwerte zulassen, kein Wert zugewiesen werden, der Null sein könnte.Furthermore, nonnullable reference types can't be assigned a value that could be null.

Verweistypen, die keine Nullwerte zulassen werden nicht überprüft, um sicherzustellen, dass sie nicht mit Null belegt oder initialisiert werden.Nullable reference types aren't checked to ensure they aren't assigned or initialized to null. Der Compiler verwendet jedoch die Flussanalyse, um sicherzustellen, dass jede Variable eines Verweistypen, der Nullwerte zulässt, gegen null geprüft wird, bevor auf sie zugegriffen oder einem nicht Verweistypen zugewiesen wird, der Nullwerte zulässt.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.

Mehr über die Funktion erfahren Sie in der Übersicht der Verweistypen, die Nullwerte zulassen.You can learn more about the feature in the overview of nullable reference types. Probieren Sie es selbst in einer neuen Anwendung in diesem Tutorial für Verweistypen, die Nullwerte zulassen aus.Try it yourself in a new application in this nullable reference types tutorial. Weitere Informationen über die Schritte zur Migration einer bestehenden Codebasis, um Verweistypen zu nutzen, die Nullwerte zulassen, finden Sie im Tutorial Migration einer Anwendung zur Verwendung Nullwerte zulassenden Verweistypen.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.

Asynchrone StreamsAsynchronous streams

Ab C# 8.0 können Sie Streams asynchron erstellen und nutzen.Starting with C# 8.0, you can create and consume streams asynchronously. Eine Methode, die einen asynchronen Stream zurückgibt, hat drei Eigenschaften:A method that returns an asynchronous stream has three properties:

  1. Die Deklaration erfolgte mit dem async-Modifikator.It's declared with the async modifier.
  2. Es wird IAsyncEnumerable<T> zurückgegeben.It returns an IAsyncEnumerable<T>.
  3. Das Verfahren enthält yield return-Anweisungen, um aufeinanderfolgende Elemente im asynchronen Stream zurückzugeben.The method contains yield return statements to return successive elements in the asynchronous stream.

Die Verwendung eines asynchronen Streams erfordert, dass Sie das Schlüsselwort await vor dem Schlüsselwort foreach hinzufügen, wenn Sie die Elemente des Streams auflisten.Consuming an asynchronous stream requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream. Das Hinzufügen des Schlüsselwortes await erfordert, dass die Methode, die den asynchronen Strom enumiert, mit dem Modifikator async deklariert wird und einen für eine async-Methode zulässigen Typ zurückgibt.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. In der Regel ist dies die Rückgabe von Task oder Task<TResult>.Typically that means returning a Task or Task<TResult>. Es kann auch ValueTask oder ValueTask<TResult> sein.It can also be a ValueTask or ValueTask<TResult>. Eine Methode kann einen asynchronen Stream sowohl verwenden als auch erzeugen, was bedeutet, dass sie IAsyncEnumerable<T> zurückgeben würde.A method can both consume and produce an asynchronous stream, which means it would return an IAsyncEnumerable<T>. Der folgende Code erzeugt eine Sequenz von 0 bis 19 und wartet 100 ms zwischen der Generierung jeder Zahl: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;
    }
}

Die Enumeration der Sequenz erfolgt mit der await foreach-Anweisung:You would enumerate the sequence using the await foreach statement:

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

Sie können asynchrone Streams selbst in unserem Tutorial zum Erstellen und Verwenden von asynchronen Streams ausprobieren.You can try asynchronous streams yourself in our tutorial on creating and consuming async streams.

Indizes und BereicheIndices and ranges

Indizes und Bereiche bieten eine prägnante Syntax für den Zugriff auf einzelne Elemente oder Bereiche in einer Sequenz.Indices and ranges provide a succinct syntax for accessing single elements or ranges in a sequence.

Diese Sprachunterstützung basiert auf zwei neuen Typen und zwei neuen Operatoren:This language support relies on two new types, and two new operators:

  • System.Index: Stellt einen Index in einer Sequenz dar.System.Index represents an index into a sequence.
  • Der Index vom Endeoperator ^, der angibt, dass ein Index relativ zum Ende der Sequenz ist.The index from end operator ^, which specifies that an index is relative to the end of the sequence.
  • System.Range: Stellt einen Unterbereich einer Sequenz dar.System.Range represents a sub range of a sequence.
  • Der Bereichsoperator .., der den Beginn und das Ende eines Bereichs als seine Operanden angibt.The range operator .., which specifies the start and end of a range as its operands.

Beginnen wir mit den Regeln für Indizes.Let's start with the rules for indexes. Betrachten Sie einen Array sequence.Consider an array sequence. Der 0-Index entspricht sequence[0].The 0 index is the same as sequence[0]. Der ^0-Index entspricht sequence[sequence.Length].The ^0 index is the same as sequence[sequence.Length]. Beachten Sie, dass sequence[^0] genau wie sequence[sequence.Length] eine Ausnahme auslöst.Note that sequence[^0] does throw an exception, just as sequence[sequence.Length] does. Für eine beliebige Zahl n ist der Index ^n identisch mit sequence.Length - n.For any number n, the index ^n is the same as sequence.Length - n.

Ein Bereich gibt den Beginn und das Ende eines Bereichs an.A range specifies the start and end of a range. Der Beginn des Bereichs ist inklusiv, das Ende des Bereichs ist jedoch exklusiv. Das bedeutet, dass der Beginn im Bereich enthalten ist, das Ende aber nicht.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. Der Bereich [0..^0] stellt ebenso wie [0..sequence.Length] den gesamten Bereich dar.The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

Schauen wir uns einige Beispiele an.Let's look at a few examples. Betrachten Sie das folgende Array, kommentiert mit seinem Index „from the start“ und „from the end“: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

Sie können das letzte Wort mit dem ^1-Index abrufen:You can retrieve the last word with the ^1 index:

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

Der folgende Code erzeugt einen Teilbereich mit den Worten „quick“, „brown“ und „fox“.The following code creates a subrange with the words "quick", "brown", and "fox". Er enthält words[1] bis words[3].It includes words[1] through words[3]. Das Element words[4] befindet sich nicht im Bereich.The element words[4] isn't in the range.

var quickBrownFox = words[1..4];

Der folgende Code erzeugt einen Teilbereich mit „lazy“ und „dog“.The following code creates a subrange with "lazy" and "dog". Dazu gehören words[^2] und words[^1].It includes words[^2] and words[^1]. Der Endindex words[^0] ist nicht enthalten:The end index words[^0] isn't included:

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

Die folgenden Beispiele erstellen Bereiche, die am Anfang, am Ende und auf beiden Seiten offen sind: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"

Sie können Bereiche auch als Variablen deklarieren:You can also declare ranges as variables:

Range phrase = 1..4;

Der Bereich kann dann innerhalb der Zeichen [ und ] verwendet werden:The range can then be used inside the [ and ] characters:

var text = words[phrase];

Indizes und Bereiche werden nicht nur von Arrays unterstützt.Not only arrays support indices and ranges. Indizes und Bereiche können auch mit string, Span<T> oder ReadOnlySpan<T> verwendet werden.You also can use indices and ranges with string, Span<T>, or ReadOnlySpan<T>. Weitere Informationen finden Sie unter Typunterstützung für Indizes und Bereiche.For more information, see Type support for indices and ranges.

Weitere Informationen zu Indizes und Bereichen finden Sie im Tutorial zu Indizes und Bereichen.You can explore more about indices and ranges in the tutorial on indices and ranges.

NULL-Coalescing-ZuweisungNull-coalescing assignment

In C# 8.0 wird der NULL-Coalescing-Zuweisungsoperator ??= eingeführt.C# 8.0 introduces the null-coalescing assignment operator ??=. Sie können den ??=-Operator verwenden, um den Wert des rechten Operanden dem linken Operanden nur dann zuzuweisen, wenn die Auswertung des linken Operanden null ergibt.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

Weitere Informationen finden Sie im Artikel zu den Operatoren ?? und ??=.For more information, see the ?? and ??= operators article.

Nicht verwaltete konstruierte TypenUnmanaged constructed types

In C# 7.3 und früher darf ein konstruierter Typ (also ein Typ, der mindestens ein Typargument enthält) kein nicht verwalteter Typ sein.In C# 7.3 and earlier, a constructed type (a type that includes at least one type argument) can't be an unmanaged type. Ab C# 8.0 ist ein konstruierter Werttyp nicht verwaltet, wenn er nur Felder von nicht verwalteten Typen enthält.Starting with C# 8.0, a constructed value type is unmanaged if it contains fields of unmanaged types only.

Ein Beispiel: Ihr Code enthält die folgende Definition des generischen Coords<T>-Typs:For example, given the following definition of the generic Coords<T> type:

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

Dann ist der Coords<int>-Typ in C# 8.0 und höher ein nicht verwalteter Typ.the Coords<int> type is an unmanaged type in C# 8.0 and later. Wie bei allen nicht verwalteten Typen können Sie einen Zeiger auf eine Variable dieses Typs erstellen oder Instanzen dieses Typs einen Arbeitsspeicherblock im Stapel zuordnen: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 }
};

Weitere Informationen finden Sie unter Nicht verwaltete Typen.For more information, see Unmanaged types.

Stackalloc in geschachtelten AusdrückenStackalloc in nested expressions

Ab C# 8.0 können Sie, wenn das Ergebnis eines stackalloc-Ausdrucks vom Typ System.Span<T> oder System.ReadOnlySpan<T> ist, den stackalloc-Ausdruck in anderen Ausdrücken verwenden: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

Erweiterung von interpolierten ausführlichen ZeichenfolgenEnhancement of interpolated verbatim strings

$- und @-Token in interpolierten ausführlichen Zeichenfolgen können in beliebiger Reihenfolge vorliegen: sowohl $@"..." als auch @$"..." sind gültige interpolierte ausführliche Zeichenfolgen.Order of the $ and @ tokens in interpolated verbatim strings can be any: both $@"..." and @$"..." are valid interpolated verbatim strings. In früheren C#-Versionen musste das Token $ vor dem Token @ vorhanden sein.In earlier C# versions, the $ token must appear before the @ token.