Novità di C# 8.0What's new in C# 8.0

C#8,0 aggiunge le funzionalità e i miglioramenti seguenti al C# linguaggio:C# 8.0 adds the following features and enhancements to the C# language:

Il resto di questo articolo descrive brevemente queste funzionalità.The remainder of this article briefly describes these features. Se sono disponibili articoli approfonditi, vengono forniti collegamenti a queste panoramiche ed esercitazioni.Where in-depth articles are available, links to those tutorials and overviews are provided. È possibile esplorare queste funzionalità nell'ambiente in uso tramite lo strumento globale dotnet try:You can explore these features in your environment using the dotnet try global tool:

  1. Installare lo strumento globale dotnet-try.Install the dotnet-try global tool.
  2. Clonare il repository dotnet/try-samples.Clone the dotnet/try-samples repository.
  3. Impostare la directory corrente sulla sottodirectory csharp8 per il repository try-samples.Set the current directory to the csharp8 subdirectory for the try-samples repository.
  4. Eseguire dotnet try.Run dotnet try.

Membri di sola letturaReadonly members

È possibile applicare il modificatore readonly ai membri di uno struct.You can apply the readonly modifier to members of a struct. Indica che il membro non modifica lo stato.It indicates that the member doesn't modify state. È più granulare rispetto all'applicazione del modificatore readonly a una dichiarazione struct.It's more granular than applying the readonly modifier to a struct declaration. Considerare lo struct modificabile seguente: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";
}

Come la maggior parte degli struct, il metodo ToString() non modifica lo stato.Like most structs, the ToString() method doesn't modify state. Si potrebbe indicare questa condizione aggiungendo il modificatore readonly alla dichiarazione di 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";

La modifica precedente genera un avviso del compilatore, perché ToString accede alla proprietà Distance, che non è contrassegnata readonly: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'

Il compilatore genera un avviso quando deve creare una copia difensiva.The compiler warns you when it needs to create a defensive copy. La proprietà Distance non modifica lo stato, quindi è possibile correggere l'avviso aggiungendo il modificatore di readonly alla dichiarazione: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);

Si noti che il modificatore readonly è necessario in una proprietà di sola lettura.Notice that the readonly modifier is necessary on a read-only property. Il compilatore non presuppone che le funzioni di accesso get non modifichino lo stato; è necessario dichiarare readonly in modo esplicito.The compiler doesn't assume get accessors don't modify state; you must declare readonly explicitly. Le proprietà implementate automaticamente sono un'eccezione. il compilatore considererà tutti i Getter implementati automaticamente come ReadOnly, quindi non è necessario aggiungere il modificatore readonly alle proprietà X e 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.

Il compilatore applica la regola che readonly membri non modificano lo stato.The compiler does enforce the rule that readonly members don't modify state. Il metodo seguente non verrà compilato a meno che non si rimuova il modificatore 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;
}

Questa funzionalità consente di specificare la finalità della progettazione in modo che il compilatore possa imporla e applicare le ottimizzazioni in base a tale finalità.This feature lets you specify your design intent so the compiler can enforce it, and make optimizations based on that intent. Per altre informazioni sui membri di sola lettura, vedere l'articolo di riferimento per il linguaggio readonly.You can learn more about readonly members in the language reference article on readonly.

Metodi di interfaccia predefinitiDefault interface methods

È ora possibile aggiungere membri alle interfacce e fornire un'implementazione per tali membri.You can now add members to interfaces and provide an implementation for those members. Questa funzionalità del linguaggio consente agli autori di API di aggiungere metodi a un'interfaccia nelle versioni più recenti senza compromettere l'origine o la compatibilità binaria con le implementazioni esistenti di tale interfaccia.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. Le implementazioni esistenti ereditano l'implementazione predefinita.Existing implementations inherit the default implementation. Questa funzionalità supporta anche l'interoperabilità di C# con API destinate ad Android o Swift, che supporta funzionalità simili.This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. I metodi di interfaccia predefiniti consentono inoltre scenari simili alla funzionalità del linguaggio "tratti".Default interface methods also enable scenarios similar to a "traits" language feature.

I metodi di interfaccia predefiniti influiscono su molti scenari ed elementi del linguaggio.Default interface methods affects many scenarios and language elements. La prima esercitazione illustra l'aggiornamento di un'interfaccia con le implementazioni predefinite.Our first tutorial covers updating an interface with default implementations. Sono previsti altre esercitazioni e aggiornamenti dei riferimenti in tempo per il rilascio generale.Other tutorials and reference updates are coming in time for general release.

Più criteri in più posizioniMore patterns in more places

I criteri di ricerca offrono strumenti per fornire funzionalità dipendenti dalla forma su tipi di dati correlati ma diversi.Pattern matching gives tools to provide shape-dependent functionality across related but different kinds of data. In C# 7.0 è stata introdotta la sintassi per i criteri di tipi e i criteri di costanti, tramite l'espressione is e l'istruzione switch.C# 7.0 introduced syntax for type patterns and constant patterns by using the is expression and the switch statement. Queste funzionalità rappresentano il primo passo per il supporto dei paradigmi di programmazione in cui i dati e la funzionalità sono separati.These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. Man mano che il settore effettua la transizione verso più microservizi e altre architetture basate sul cloud, diventano necessari altri strumenti del linguaggio.As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed.

C# 8.0 espande questo vocabolario, in modo da poter usare più espressioni di criteri in più posizioni nel codice.C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. Prendere in considerazione queste funzionalità quando i dati e le funzionalità sono separati.Consider these features when your data and functionality are separate. Prendere in considerazione i criteri di ricerca quando gli algoritmi dipendono da un fatto diverso dal tipo di runtime di un oggetto.Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. Queste tecniche offrono un altro modo per esprimere le progettazioni.These techniques provide another way to express designs.

Oltre ai nuovi criteri disponibili in nuove posizioni, C# 8.0 introduce i criteri ricorsivi.In addition to new patterns in new places, C# 8.0 adds recursive patterns. Il risultato di qualsiasi espressione di criteri è un'espressione.The result of any pattern expression is an expression. Un criterio ricorsivo è semplicemente un'espressione di criteri applicata all'output di un'altra espressione di criteri.A recursive pattern is simply a pattern expression applied to the output of another pattern expression.

Espressioni switchSwitch expressions

Spesso, un'istruzione switch produce un valore in ognuno dei relativi blocchi case.Often, a switch statement produces a value in each of its case blocks. Le espressioni switch consentono di usare una sintassi più concisa per le espressioniSwitch expressions enable you to use more concise expression syntax. con meno parole chiave case e break ripetitive e un numero inferiore di parentesi graffe.There are fewer repetitive case and break keywords, and fewer curly braces. Ad esempio, si consideri l'enumerazione seguente che elenca i colori dell'arcobaleno:As an example, consider the following enum that lists the colors of the rainbow:

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

Se l'applicazione ha definito un tipo RGBColor costituito dai componenti R, G e B, è possibile convertire un valore Rainbow nei relativi valori RGB usando il metodo seguente che contiene un'espressione switch: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)),
    };

Questo esempio include numerosi miglioramenti della sintassi:There are several syntax improvements here:

  • La variabile precede la parola chiave switch.The variable comes before the switch keyword. L'ordine diverso rende più semplice distinguere visivamente l'espressione switch dall'istruzione switch.The different order makes it visually easy to distinguish the switch expression from the switch statement.
  • Gli elementi case e : vengono sostituiti con =>.The case and : elements are replaced with =>. È più conciso e intuitivo.It's more concise and intuitive.
  • Il caso default è sostituito con un discard _.The default case is replaced with a _ discard.
  • I corpi sono espressioni e non istruzioni.The bodies are expressions, not statements.

Confrontare questo codice con quello equivalente che usa l'istruzione switch classica: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));
    };
}

Criteri per le proprietàProperty patterns

I criteri per le proprietà consentono di individuare corrispondenze in base alle proprietà dell'oggetto esaminato.The property pattern enables you to match on properties of the object examined. Si consideri un sito di e-commerce che deve calcolare le imposte sulle vendite in base all'indirizzo dell'acquirente.Consider an eCommerce site that must compute sales tax based on the buyer's address. Tale calcolo non è un compito fondamentale di una classe Address.That computation isn't a core responsibility of an Address class. È soggetto a variazioni nel tempo, probabilmente più spesso rispetto a eventuali modifiche del formato dell'indirizzo.It will change over time, likely more often than address format changes. L'importo delle imposte sulle vendite dipende dalla proprietà State dell'indirizzo.The amount of sales tax depends on the State property of the address. Il metodo seguente usa i criteri per le proprietà per calcolare l'imposta sulle vendite dall'indirizzo e dal prezzo: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
    };

I criteri di ricerca creano una sintassi concisa per esprimere questo algoritmo.Pattern matching creates a concise syntax for expressing this algorithm.

Criteri per le tupleTuple patterns

Alcuni algoritmi dipendono da più input.Some algorithms depend on multiple inputs. I criteri per le tuple consentono di passare da un valore a un altro, espressi come tupla.Tuple patterns allow you to switch based on multiple values expressed as a tuple. Il codice seguente illustra un'espressione switch per il gioco rock, paper, scissors (carta-forbice-sasso):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"
    };

I messaggi indicano il vincitore.The messages indicate the winner. Il caso discard rappresenta le tre combinazioni di valori equivalenti o altri input di testo.The discard case represents the three combinations for ties, or other text inputs.

Criteri per la posizionePositional patterns

Alcuni tipi includono un metodo Deconstruct che decostruisce le proprietà in variabili discrete.Some types include a Deconstruct method that deconstructs its properties into discrete variables. Quando un metodo Deconstruct è accessibile, è possibile usare i criteri per la posizione per esaminare le proprietà dell'oggetto e usare tali proprietà per un criterio.When a Deconstruct method is accessible, you can use positional patterns to inspect properties of the object and use those properties for a pattern. Considerare la classe Point seguente che include un metodo Deconstruct per creare variabili discrete per X e Y: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);
}

Considerare anche l'enumerazione seguente, che rappresenta posizioni diverse in un quadrante:Additionally, consider the following enum that represents various positions of a quadrant:

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

Il metodo seguente usa i criteri per la posizione per estrarre i valori di x e y.The following method uses the positional pattern to extract the values of x and y. Quindi usa una clausola when per determinare l'elemento Quadrant del punto: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
};

I criteri discard nell'espressione switch precedente trovano una corrispondenza quando x o y è uguale a 0, ma non entrambi.The discard pattern in the preceding switch matches when either x or y is 0, but not both. Un'espressione switch deve produrre un valore o generare un'eccezione.A switch expression must either produce a value or throw an exception. Se nessuno dei casi corrisponde, l'espressione switch genera un'eccezione.If none of the cases match, the switch expression throws an exception. Il compilatore genera un avviso se non si coprono tutti i casi possibili nell'espressione switch.The compiler generates a warning for you if you don't cover all possible cases in your switch expression.

È possibile esplorare le tecniche dei criteri di ricerca in questa esercitazione avanzata sui criteri di ricerca.You can explore pattern matching techniques in this advanced tutorial on pattern matching.

Dichiarazioni usingUsing declarations

Una dichiarazione using è una dichiarazione di variabile preceduta dalla parola chiave using.A using declaration is a variable declaration preceded by the using keyword. Indica al compilatore che la variabile dichiarata deve essere eliminata alla fine dell'ambito di inclusione.It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope. Ad esempio, si consideri il codice seguente che consente di scrivere un file di testo: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
}

Nell'esempio precedente il file viene eliminato quando viene raggiunta la parentesi graffa di chiusura per il metodo.In the preceding example, the file is disposed when the closing brace for the method is reached. Questa è la fine dell'ambito in cui viene dichiarato file.That's the end of the scope in which file is declared. Il codice precedente è equivalente al codice seguente con l'istruzione using classica: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;
}

Nell'esempio precedente il file viene eliminato quando viene raggiunta la parentesi graffa di chiusura associata all'istruzione using.In the preceding example, the file is disposed when the closing brace associated with the using statement is reached.

In entrambi i casi, il compilatore genera la chiamata a Dispose().In both cases, the compiler generates the call to Dispose(). Il compilatore genera un errore se l'espressione nell'istruzione using non è Disposable.The compiler generates an error if the expression in the using statement isn't disposable.

Funzioni locali staticheStatic local functions

È ora possibile aggiungere il modificatore static alle funzioni locali per assicurarsi che tale funzione locale non acquisisca (faccia riferimento a) variabili dall'ambito di inclusione.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. In questo modo viene generato l'errore CS8421 "A static local function can't contain a reference to <variable>" (Una funzione locale statica non può fare riferimento a ).Doing so generates CS8421, "A static local function can't contain a reference to <variable>."

Si consideri il codice seguente.Consider the following code. La funzione locale LocalFunction accede alla variabile y, dichiarata nell'ambito di inclusione (il metodo M).The local function LocalFunction accesses the variable y, declared in the enclosing scope (the method M). Pertanto, non è possibile dichiarare LocalFunction con il modificatore static:Therefore, LocalFunction can't be declared with the static modifier:

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

    void LocalFunction() => y = 0;
}

Il codice seguente contiene una funzione locale statica.The following code contains a static local function. Può essere statica perché non accede ad alcuna variabile nell'ambito di inclusione: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;
}

Struct ref DisposableDisposable ref structs

Una struct dichiarata con il modificatore di ref non può implementare alcuna interfaccia e pertanto non può implementare IDisposable.A struct declared with the ref modifier may not implement any interfaces and so can't implement IDisposable. Pertanto, per abilitare un ref struct per l'eliminazione, deve avere un metodo void Dispose() accessibile.Therefore, to enable a ref struct to be disposed, it must have an accessible void Dispose() method. Questa funzionalità si applica anche alle dichiarazioni readonly ref struct.This feature also applies to readonly ref struct declarations.

Tipi riferimento nullableNullable reference types

All'interno di un contesto delle annotazioni nullable, qualsiasi variabile di un tipo riferimento viene considerata come un tipo riferimento non nullable.Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. Se si vuole indicare che una variabile può essere Null, è necessario aggiungere ? al nome del tipo per dichiarare la variabile come un tipo riferimento nullable.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.

Per i tipi riferimento non nullable, il compilatore usa l'analisi di flusso per garantire che le variabili locali vengano inizializzate su un valore diverso da Null al momento della dichiarazione.For nonnullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to a non-null value when declared. I campi devono essere inizializzati durante la costruzione.Fields must be initialized during construction. Il compilatore genera un avviso se la variabile non è impostata da una chiamata a uno dei costruttori disponibili o da un inizializzatore.The compiler generates a warning if the variable isn't set by a call to any of the available constructors or by an initializer. Inoltre, non è possibile assegnare ai tipi riferimento non nullable un valore che potrebbe essere Null.Furthermore, nonnullable reference types can't be assigned a value that could be null.

I tipi riferimento nullable non vengono controllati per verificare che non vengano assegnati o inizializzati su Null.Nullable reference types aren't checked to ensure they aren't assigned or initialized to null. Tuttavia, il compilatore usa l'analisi di flusso per garantire che qualsiasi variabile di un tipo riferimento nullable venga controllata per i valori Null prima dell'accesso o prima che venga assegnata a un tipo riferimento non nullable.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.

Altre informazioni su questa funzionalità sono disponibili nella panoramica dei tipi riferimento nullable.You can learn more about the feature in the overview of nullable reference types. È possibile provare in autonomia in una nuova applicazione in questa esercitazione sui tipi riferimento nullable.Try it yourself in a new application in this nullable reference types tutorial. Per informazioni sulla procedura per eseguire la migrazione di una base di codice esistente per l'uso dei tipi riferimento nullable, vedere l'esercitazione sulla migrazione di un'applicazione per l'uso dei tipi riferimento nullable.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.

Flussi asincroniAsynchronous streams

A partire da C# 8.0, è possibile creare e utilizzare i flussi in modo asincrono.Starting with C# 8.0, you can create and consume streams asynchronously. Un metodo che restituisce un flusso asincrono ha tre proprietà:A method that returns an asynchronous stream has three properties:

  1. È stato dichiarato con il modificatore async.It's declared with the async modifier.
  2. Restituisce IAsyncEnumerable<T>.It returns an IAsyncEnumerable<T>.
  3. Il metodo contiene istruzioni yield return per restituire gli elementi successivi nel flusso asincrono.The method contains yield return statements to return successive elements in the asynchronous stream.

Per utilizzare un flusso asincrono, è necessario aggiungere la parola chiave await prima della parola chiave foreach quando si enumerano gli elementi del flusso.Consuming an asynchronous stream requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream. Per l'aggiunta della parola chiave await, il metodo che enumera il flusso asincrono deve essere dichiarato con il modificatore async e restituire un tipo consentito per un metodo 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. In genere ciò significa restituire Task o Task<TResult>.Typically that means returning a Task or Task<TResult>. Può anche essere ValueTask o ValueTask<TResult>.It can also be a ValueTask or ValueTask<TResult>. Un metodo può sia utilizzare che produrre un flusso asincrono, il che significa che restituirebbe IAsyncEnumerable<T>.A method can both consume and produce an asynchronous stream, which means it would return an IAsyncEnumerable<T>. Il codice seguente genera una sequenza da 0 a 19, con un'attesa di 100 ms tra la generazione di ogni numero: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;
    }
}

La sequenza viene enumerata usando l'istruzione await foreach:You would enumerate the sequence using the await foreach statement:

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

È possibile provare i flussi asincroni in autonomia nell'esercitazione dedicata alla creazione e all'utilizzo di flussi asincroni.You can try asynchronous streams yourself in our tutorial on creating and consuming async streams.

Indici e intervalliIndices and ranges

Gli indici e gli intervalli forniscono una sintassi concisa per l'accesso a singoli elementi o intervalli in una sequenza.Indices and ranges provide a succinct syntax for accessing single elements or ranges in a sequence.

Questo supporto per il linguaggio si basa su due nuovi tipi e due nuovi operatori:This language support relies on two new types, and two new operators:

  • System.Index rappresenta un indice in una sequenza.System.Index represents an index into a sequence.
  • Indice dell'operatore end ^, che specifica che un indice è relativo alla fine della sequenza.The index from end operator ^, which specifies that an index is relative to the end of the sequence.
  • System.Range rappresenta un intervallo secondario di una sequenza.System.Range represents a sub range of a sequence.
  • Operatore di intervallo .., che specifica l'inizio e la fine di un intervallo come operandi.The range operator .., which specifies the start and end of a range as its operands.

Per iniziare, ecco come funzionano le regole per gli indici.Let's start with the rules for indexes. Prendere in considerazione una matrice sequence.Consider an array sequence. L'indice 0 è uguale a sequence[0].The 0 index is the same as sequence[0]. L'indice ^0 è uguale a sequence[sequence.Length].The ^0 index is the same as sequence[sequence.Length]. Si noti che sequence[^0] genera un'eccezione, proprio come sequence[sequence.Length].Note that sequence[^0] does throw an exception, just as sequence[sequence.Length] does. Per qualsiasi numero n, l'indice ^n è uguale a sequence.Length - n.For any number n, the index ^n is the same as sequence.Length - n.

Un intervallo specifica inizio e fine di un intervallo.A range specifies the start and end of a range. L'inizio dell'intervallo è inclusivo, ma la fine dell'intervallo è esclusiva, ovvero l' inizio è incluso nell'intervallo, ma la fine non è inclusa nell'intervallo.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. L'intervallo [0..^0] rappresenta l'intero intervallo, proprio come [0..sequence.Length] rappresenta l'intero intervallo.The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

Di seguito verranno esaminati alcuni esempi.Let's look at a few examples. Si consideri la matrice seguente, annotata con il relativo indice dall'inizio e dalla fine: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

È possibile recuperare l'ultima parola con l'indice ^1:You can retrieve the last word with the ^1 index:

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

Il codice seguente crea un intervallo secondario con le parole "quick", "brown" e "fox".The following code creates a subrange with the words "quick", "brown", and "fox". Include da words[1] a words[3].It includes words[1] through words[3]. L'elemento words[4] non è compreso nell'intervallo.The element words[4] isn't in the range.

var quickBrownFox = words[1..4];

Il codice seguente crea un intervallo secondario con "lazy" e "dog".The following code creates a subrange with "lazy" and "dog". Include words[^2] e words[^1].It includes words[^2] and words[^1]. Il words[^0] end index non è incluso:The end index words[^0] isn't included:

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

Gli esempi seguenti creano intervalli aperti alle estremità, per l'inizio, la fine o entrambe: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"

È anche possibile dichiarare gli intervalli come variabili:You can also declare ranges as variables:

Range phrase = 1..4;

L'intervallo può quindi essere usato all'interno dei caratteri [ e ]:The range can then be used inside the [ and ] characters:

var text = words[phrase];

Non solo le matrici supportano gli indici e gli intervalli.Not only arrays support indices and ranges. È anche possibile usare gli indici e gli intervalli con String, Span<T>o ReadOnlySpan<T>.You also can use indices and ranges with string, Span<T>, or ReadOnlySpan<T>. Per altre informazioni, vedere supporto dei tipi per indici e intervalli.For more information, see Type support for indices and ranges.

È possibile ottenere maggiori informazioni su indici e intervalli nell'esercitazione Indici e intervalli.You can explore more about indices and ranges in the tutorial on indices and ranges.

Assegnazione di Unione nullNull-coalescing assignment

C#8,0 introduce l'operatore di assegnazione a Unione null ??=.C# 8.0 introduces the null-coalescing assignment operator ??=. È possibile usare l'operatore ??= per assegnare il valore dell'operando destro all'operando sinistro solo se l'operando sinistro restituisce 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

Per ulteriori informazioni, vedere ?? e?? = articolo Operators .For more information, see the ?? and ??= operators article.

Tipi costruiti non gestitiUnmanaged constructed types

In C# 7,3 e versioni precedenti, un tipo costruito (un tipo che include almeno un argomento di tipo) non può essere un tipo non gestito.In C# 7.3 and earlier, a constructed type (a type that includes at least one type argument) can't be an unmanaged type. A partire C# da 8,0, un tipo di valore costruito non è gestito se contiene campi solo di tipi non gestiti.Starting with C# 8.0, a constructed value type is unmanaged if it contains fields of unmanaged types only.

Ad esempio, data la seguente definizione del tipo di Coords<T> generico:For example, given the following definition of the generic Coords<T> type:

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

il tipo di Coords<int> è un tipo non gestito in C# 8,0 e versioni successive.the Coords<int> type is an unmanaged type in C# 8.0 and later. Come per qualsiasi tipo non gestito, è possibile creare un puntatore a una variabile di questo tipo o allocare un blocco di memoria nello stack per le istanze di questo tipo: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 }
};

Per ulteriori informazioni, vedere tipi non gestiti.For more information, see Unmanaged types.

stackalloc nelle espressioni annidateStackalloc in nested expressions

A partire C# da 8,0, se il risultato di un'espressione stackalloc è di tipoSystem.Span<T>oSystem.ReadOnlySpan<T>, è possibile usare l'espressionestackallocin altre espressioni: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

Miglioramento delle stringhe verbatim interpolateEnhancement of interpolated verbatim strings

L'ordine dei token $ e @ nelle stringhe verbatim interpolate può essere qualsiasi: sia $@"..." che @$"..." sono stringhe verbatim interpolate valide.Order of the $ and @ tokens in interpolated verbatim strings can be any: both $@"..." and @$"..." are valid interpolated verbatim strings. Nelle versioni C# precedenti, il token di $ deve essere visualizzato prima del token di @.In earlier C# versions, the $ token must appear before the @ token.