Novidades no C# 8.0What's new in C# 8.0

O c# 8,0 adiciona os seguintes recursos e aprimoramentos à linguagem C#:C# 8.0 adds the following features and enhancements to the C# language:

O C# 8,0 tem suporte no .NET Core 3. x e .net Standard 2,1.C# 8.0 is supported on .NET Core 3.x and .NET Standard 2.1. Para obter mais informações, consulte controle de versão da linguagem C#.For more information, see C# language versioning.

O restante deste artigo descreve rapidamente esses recursos.The remainder of this article briefly describes these features. Quando houver artigos detalhados disponíveis, forneceremos links para esses tutoriais e visões gerais.Where in-depth articles are available, links to those tutorials and overviews are provided. Você pode explorar esses recursos em seu ambiente usando a ferramenta global dotnet try:You can explore these features in your environment using the dotnet try global tool:

  1. Instale a ferramenta global dotnet-try.Install the dotnet-try global tool.
  2. Clone o repositório dotnet/try-samples.Clone the dotnet/try-samples repository.
  3. Defina o diretório atual do subdiretório csharp8 para o repositório try-samples.Set the current directory to the csharp8 subdirectory for the try-samples repository.
  4. Execute dotnet try.Run dotnet try.

Membros somente leituraReadonly members

Você pode aplicar o readonly modificador a membros de um struct.You can apply the readonly modifier to members of a struct. Indica que o membro não modifica o estado.It indicates that the member doesn't modify state. É mais granular do que aplicar o modificador readonly a uma declaração struct.It's more granular than applying the readonly modifier to a struct declaration. Considere o seguinte struct mutável: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";
}

Como a maioria das structs, o ToString() método não modifica o estado.Like most structs, the ToString() method doesn't modify state. É possível indicar isso adicionando o modificador readonly à declaração de 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";

A alteração anterior gera um aviso do compilador, pois ToString acessa a Distance propriedade, que não está marcada 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'

O compilador avisa quando há a necessidade de criar uma cópia de defesa.The compiler warns you when it needs to create a defensive copy. A Distance propriedade não muda de estado, portanto você pode corrigir esse aviso adicionando o readonly modificador à declaração: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);

Observe que o readonly modificador é necessário em uma propriedade somente leitura.Notice that the readonly modifier is necessary on a read-only property. O compilador não pressupõe get que os acessadores não modifiquem o estado; você deve declarar readonly explicitamente.The compiler doesn't assume get accessors don't modify state; you must declare readonly explicitly. As propriedades implementadas automaticamente são uma exceção; o compilador tratará todos os getters autoimplementados como readonly , portanto, aqui não há necessidade de adicionar o readonly modificador X às Y Propriedades e.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.

O compilador impõe a regra que readonly os membros não modificam o estado.The compiler does enforce the rule that readonly members don't modify state. O método a seguir não será compilado, a menos que você remova o readonly modificador:The following method won't compile unless you remove the readonly modifier:

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

Esse recurso permite que você especifique sua intenção de design para que o compilador possa impô-la e faça otimizações com base nessa intenção.This feature lets you specify your design intent so the compiler can enforce it, and make optimizations based on that intent.

Para obter mais informações, consulte a seção readonly membros da instância do artigo tipos de estrutura .For more information, see the readonly instance members section of the Structure types article.

Métodos de interface padrãoDefault interface methods

Agora é possível adicionar membros a interfaces e fornecer uma implementação para esses membros.You can now add members to interfaces and provide an implementation for those members. Esse recurso de linguagem permite que os autores de API adicionem métodos a uma interface em versões posteriores sem interromper a fonte ou a compatibilidade binária com implementações existentes dessa interface.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. As implementações existentes herdam a implementação padrão.Existing implementations inherit the default implementation. Esse recurso também permite que o C# interopere com APIs que direcionam o Android ou o Swift, que dão suporte a recursos semelhantes.This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. Os métodos de interface padrão também habilitam cenários semelhantes a um recurso de linguagem de "características".Default interface methods also enable scenarios similar to a "traits" language feature.

Os métodos de interface padrão afetam muitos cenários e elementos de linguagem.Default interface methods affect many scenarios and language elements. Nosso primeiro tutorial aborda como atualizar uma interface com implementações padrão.Our first tutorial covers updating an interface with default implementations. Outros tutoriais e atualizações de referência chegarão a tempo para a versão geral.Other tutorials and reference updates are coming in time for general release.

Mais padrões em mais partesMore patterns in more places

Com a correspondência de padrões, você recebe ferramentas para fornecer funcionalidades dependentes da forma em tipos de dados relacionados, mas diferentes.Pattern matching gives tools to provide shape-dependent functionality across related but different kinds of data. O C# 7,0 introduziu a sintaxe para padrões de tipo e padrões constantes usando a is expressão e a switch instrução.C# 7.0 introduced syntax for type patterns and constant patterns by using the is expression and the switch statement. Esses recursos representaram os primeiros passos em direção ao suporte a paradigmas de programação, em que os dados e a funcionalidade vivem separados.These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. À medida que o setor se aproxima mais de microsserviços e de outras arquiteturas baseadas em nuvem, outras ferramentas de linguagem de tornam necessárias.As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed.

O C# 8.0 expande esse vocabulário, para que você possa usar mais expressões de padrão em mais partes do seu código.C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. Considere esses recursos quando seus dados e funcionalidades estiverem separados.Consider these features when your data and functionality are separate. Considere a correspondência de padrões quando seus algoritmos dependerem de um fato diferente do tipo de runtime de um objeto.Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. Essas técnicas fornecem outra maneira de expressar designs.These techniques provide another way to express designs.

Além dos novos padrões em novas partes, o C# 8.0 adiciona padrões recursivos.In addition to new patterns in new places, C# 8.0 adds recursive patterns. O resultado de qualquer expressão padrão é uma expressão.The result of any pattern expression is an expression. Um padrão recursivo é simplesmente uma expressão padrão aplicada à saída de outra expressão padrão.A recursive pattern is simply a pattern expression applied to the output of another pattern expression.

Expressões switchSwitch expressions

Geralmente, uma switch instrução produz um valor em cada um de seus case blocos.Often, a switch statement produces a value in each of its case blocks. As expressões switch permitem que você use a sintaxe de expressão mais concisa.Switch expressions enable you to use more concise expression syntax. Há menos palavras-chave case e break repetidas, e menos chaves.There are fewer repetitive case and break keywords, and fewer curly braces. Por exemplo, considere a enumeração a seguir que lista as cores do arco-íris: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 seu aplicativo definiu um tipo RGBColor que é construído a partir dos componentes R, G e B, você poderia converter um valor Rainbow em seus valores RGB usando o método a seguir que contém uma expressão de opção: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)),
    };

Há vários aprimoramentos de sintaxe aqui:There are several syntax improvements here:

  • A variável vem antes da palavra-chave switch.The variable comes before the switch keyword. A ordem diferente facilita distinguir visualmente a expressão switch da instrução switch.The different order makes it visually easy to distinguish the switch expression from the switch statement.
  • Os elementos case e : são substituídos por =>.The case and : elements are replaced with =>. É mais conciso e intuitivo.It's more concise and intuitive.
  • O caso default é substituído por um descarte _.The default case is replaced with a _ discard.
  • Os corpos são expressões, não instruções.The bodies are expressions, not statements.

Compare isso com o código equivalente usando a instrução clássica 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));
    };
}

Padrões da propriedadeProperty patterns

O padrão da propriedade permite que você compare as propriedades do objeto examinado.The property pattern enables you to match on properties of the object examined. Considere um site de comércio eletrônico que deve calcular o imposto da venda com base no endereço do comprador.Consider an eCommerce site that must compute sales tax based on the buyer's address. Essa computação não é uma responsabilidade principal de uma Address classe.That computation isn't a core responsibility of an Address class. Ele mudará ao longo do tempo, provavelmente com mais frequência do que as alterações de formato de endereço.It will change over time, likely more often than address format changes. O valor do imposto depende da propriedade State do endereço.The amount of sales tax depends on the State property of the address. O método a seguir usa o padrão de propriedade para calcular o imposto da venda de acordo com o endereço e o preço: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
    };

A correspondência de padrão cria uma sintaxe concisa para expressar esse algoritmo.Pattern matching creates a concise syntax for expressing this algorithm.

Padrões de tuplaTuple patterns

Alguns algoritmos dependem de várias entradas.Some algorithms depend on multiple inputs. Padrões de tupla permitem que você alterne com base em vários valores, expressadas como uma tupla.Tuple patterns allow you to switch based on multiple values expressed as a tuple. O código a seguir mostra uma expressão de comutador para o jogo pedra, papel, tesoura: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"
    };

As mensagens indicam o vencedor.The messages indicate the winner. O caso de descarte representa três combinações para empates ou outras entradas de texto.The discard case represents the three combinations for ties, or other text inputs.

Padrões posicionaisPositional patterns

Alguns tipos incluem um método Deconstruct que desconstrói suas propriedades em variáveis discretas.Some types include a Deconstruct method that deconstructs its properties into discrete variables. Quando um método Deconstruct é acessível, você pode usar padrões posicionais para inspecionar as propriedades do objeto e usar essas propriedades para um padrão.When a Deconstruct method is accessible, you can use positional patterns to inspect properties of the object and use those properties for a pattern. Considere a seguinte classe Point, que inclui um método Deconstruct para criar variáveis discretas para 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);
}

Além disso, considere a seguinte enumeração que representa várias posições de um quadrante:Additionally, consider the following enum that represents various positions of a quadrant:

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

O método a seguir usa o padrão posicional para extrair os valores de x e y.The following method uses the positional pattern to extract the values of x and y. Em seguida, ele usa uma cláusula when para determinar o Quadrant do ponto: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
};

O padrão de discard na opção anterior encontra a correspondência quando x ou y é 0, mas não ambos.The discard pattern in the preceding switch matches when either x or y is 0, but not both. Uma expressão switch deve produzir um valor ou lançar uma exceção.A switch expression must either produce a value or throw an exception. Se não houver correspondência em nenhum dos casos, a expressão switch gerará uma exceção.If none of the cases match, the switch expression throws an exception. O compilador gerará um aviso para você se você não cobrir todos os casos possíveis em sua expressão de comutador.The compiler generates a warning for you if you don't cover all possible cases in your switch expression.

Explore técnicas de correspondência de padrões neste tutorial avançado sobre correspondência de padrões.You can explore pattern matching techniques in this advanced tutorial on pattern matching.

Declarações usingUsing declarations

Uma declaração using é uma declaração de variável precedida pela palavra-chave using.A using declaration is a variable declaration preceded by the using keyword. Ele informa ao compilador que a variável que está sendo declarada deve ser descartada ao final do escopo delimitador.It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope. Por exemplo, considere o seguinte código que grava um arquivo de texto: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
}

No exemplo anterior, o arquivo é descartado quando a chave de fechamento do método é atingida.In the preceding example, the file is disposed when the closing brace for the method is reached. Esse é o final do escopo no qual file é declarado.That's the end of the scope in which file is declared. O código anterior equivale ao código a seguir que usa as instruções using clássicas: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;
}

No exemplo anterior, o arquivo é descartado quando a chave de fechamento associada à instrução using é atingida.In the preceding example, the file is disposed when the closing brace associated with the using statement is reached.

Em ambos os casos, o compilador gera a chamada para Dispose().In both cases, the compiler generates the call to Dispose(). O compilador gerará um erro se a expressão na using instrução não for descartável.The compiler generates an error if the expression in the using statement isn't disposable.

Funções locais estáticasStatic local functions

Agora você pode adicionar o modificador static para funções locais a fim de garantir que essa função local não capture (faça referência) às variáveis no escopo delimitador.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. Isso gera CS8421, "Uma função local estática não pode conter uma referência a <variable>".Doing so generates CS8421, "A static local function can't contain a reference to <variable>."

Considere o código a seguir.Consider the following code. A função local LocalFunction acessa a variável y, declarada no escopo delimitador (o método M).The local function LocalFunction accesses the variable y, declared in the enclosing scope (the method M). Portanto, LocalFunction não pode ser declarada com o modificador static:Therefore, LocalFunction can't be declared with the static modifier:

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

    void LocalFunction() => y = 0;
}

O código a seguir contém uma função local estática.The following code contains a static local function. Ela pode ser estática porque não acesse as variáveis no escopo delimitador: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;
}

Estruturas ref descartáveisDisposable ref structs

Um struct declarado com o ref modificador não pode implementar nenhuma interface e, portanto, não pode implementar IDisposable .A struct declared with the ref modifier may not implement any interfaces and so can't implement IDisposable. Portanto, para permitir que uma ref struct seja descartada, ela deve ter um método void Dispose() acessível.Therefore, to enable a ref struct to be disposed, it must have an accessible void Dispose() method. Esse recurso também se aplica a readonly ref struct declarações.This feature also applies to readonly ref struct declarations.

Tipos de referência anuláveisNullable reference types

Dentro de um contexto de anotação anulável, qualquer variável de um tipo de referência é considerado um tipo de referência não anulável.Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. Se você quiser indicar que uma variável pode ser nula, acrescente o nome do tipo com o ? para declarar a variável como um tipo de referência anulável.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.

Para tipos de referência não anuláveis, o compilador usa a análise de fluxo para garantir que as variáveis locais sejam inicializadas como um valor não nulo quando declaradas.For nonnullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to a non-null value when declared. Os campos devem ser inicializados durante a construção.Fields must be initialized during construction. O compilador gerará um aviso se a variável não for definida por uma chamada para qualquer um dos construtores disponíveis ou por um inicializador.The compiler generates a warning if the variable isn't set by a call to any of the available constructors or by an initializer. Além disso, os tipos de referência não anuláveis não podem receber um valor que possa ser nulo.Furthermore, nonnullable reference types can't be assigned a value that could be null.

Os tipos de referência anuláveis não são verificados para garantir que não tenham sido atribuídos ou inicializados como nulos.Nullable reference types aren't checked to ensure they aren't assigned or initialized to null. No entanto, o compilador usa a análise de fluxo para garantir que qualquer variável de um tipo de referência anulável passe por verificação com relação a um nulo antes de ser acessada ou atribuída a um tipo de referência não anulável.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.

Saiba mais sobre o recurso na visão geral sobre Tipos de referência anuláveis.You can learn more about the feature in the overview of nullable reference types. Experimente-o em um novo aplicativo neste tutorial de tipos de referência anuláveis.Try it yourself in a new application in this nullable reference types tutorial. Saiba mais sobre as etapas para migrar uma base de códigos existente para usar tipos de referência anuláveis no tutorial sobre como migrar um aplicativo para usar tipos de referência anuláveis.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.

Fluxos assíncronosAsynchronous streams

A partir do C# 8.0, você pode criar e consumir fluxos de forma assíncrona.Starting with C# 8.0, you can create and consume streams asynchronously. Um método que retorna um fluxo assíncrono tem três propriedades:A method that returns an asynchronous stream has three properties:

  1. Ele é declarado com o modificador async.It's declared with the async modifier.
  2. Ela retorna um IAsyncEnumerable<T>.It returns an IAsyncEnumerable<T>.
  3. O método contém instruções yield return para retornar elementos sucessivos no fluxo assíncrono.The method contains yield return statements to return successive elements in the asynchronous stream.

O consumo de um fluxo assíncrono exige que você adicione a palavra-chave await antes da palavra-chave foreach quando você enumera os elementos do fluxo.Consuming an asynchronous stream requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream. A adição da palavra-chave await exige o método que enumera o fluxo assíncrono seja declarado com o modificador async e retorne um tipo permitido para um método 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. Normalmente, isso significa retornar um Task ou Task<TResult>.Typically that means returning a Task or Task<TResult>. Também pode ser ValueTask ou ValueTask<TResult>.It can also be a ValueTask or ValueTask<TResult>. Um método pode consumir e produzir um fluxo assíncrono, o que significa que retornaria um IAsyncEnumerable<T>.A method can both consume and produce an asynchronous stream, which means it would return an IAsyncEnumerable<T>. O código a seguir gera uma sequência de 0 a 19, esperando 100 ms entre a geração de cada número: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;
    }
}

Você enumeraria a sequência usando a instrução await foreach:You would enumerate the sequence using the await foreach statement:

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

Experimente você mesmo os fluxos assíncronos em nosso tutorial sobre como criar e consumir fluxos assíncronos.You can try asynchronous streams yourself in our tutorial on creating and consuming async streams. Por padrão, os elementos de fluxo são processados no contexto capturado.By default, stream elements are processed in the captured context. Se você quiser desabilitar a captura do contexto, use o TaskAsyncEnumerableExtensions.ConfigureAwait método de extensão.If you want to disable capturing of the context, use the TaskAsyncEnumerableExtensions.ConfigureAwait extension method. Para obter mais informações sobre contextos de sincronização e como capturar o contexto atual, consulte o artigo sobre como consumir o padrão assíncrono baseado em tarefa.For more information about synchronization contexts and capturing the current context, see the article on consuming the Task-based asynchronous pattern.

Descartável assíncronoAsynchronous disposable

A partir do C# 8,0, a linguagem dá suporte a tipos descartáveis assíncronos que implementam a System.IAsyncDisposable interface.Starting with C# 8.0, the language supports asynchronous disposable types that implement the System.IAsyncDisposable interface. Você usa a await using instrução para trabalhar com um objeto descartável de forma assíncrona.You use the await using statement to work with an asynchronously disposable object. Para obter mais informações, consulte o artigo implementar um método DisposeAsync .For more information, see the Implement a DisposeAsync method article.

Índices e intervalosIndices and ranges

Índices e intervalos fornecem uma sintaxe sucinta para acessar elementos únicos ou intervalos em uma sequência.Indices and ranges provide a succinct syntax for accessing single elements or ranges in a sequence.

Esse suporte a idioma depende de dois novos tipos e de dois novos operadores:This language support relies on two new types, and two new operators:

  • System.Index representa um índice em uma sequência.System.Index represents an index into a sequence.
  • O índice do operador end ^ , que especifica que um índice é relativo ao final da sequência.The index from end operator ^, which specifies that an index is relative to the end of the sequence.
  • System.Range representa um subintervalo de uma sequência.System.Range represents a sub range of a sequence.
  • O operador Range .. , que especifica o início e o término de um intervalo como seus operandos.The range operator .., which specifies the start and end of a range as its operands.

Vamos começar com as regras para índices.Let's start with the rules for indexes. Considere uma matriz sequence.Consider an array sequence. O índice 0 é o mesmo que sequence[0].The 0 index is the same as sequence[0]. O índice ^0 é o mesmo que sequence[sequence.Length].The ^0 index is the same as sequence[sequence.Length]. Observe que sequence[^0] gera uma exceção, assim como sequence[sequence.Length] faz.Note that sequence[^0] does throw an exception, just as sequence[sequence.Length] does. Para qualquer número n, o índice ^n é o mesmo que sequence.Length - n.For any number n, the index ^n is the same as sequence.Length - n.

Um intervalo especifica o início e o final de um intervalo.A range specifies the start and end of a range. O início do intervalo é inclusivo, mas o final do intervalo é exclusivo, o que significa que o início é incluído no intervalo, mas o final não é incluído no intervalo.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. O intervalo [0..^0] representa todo o intervalo, assim como [0..sequence.Length] representa todo o intervalo.The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

Vamos ver alguns exemplos.Let's look at a few examples. Considere a matriz a seguir, anotada com seu índice do início e do final: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

Recupere a última palavra com o índice ^1:You can retrieve the last word with the ^1 index:

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

O código a seguir cria um subintervalo com as palavras "quick", "brown" e "fox".The following code creates a subrange with the words "quick", "brown", and "fox". Ele inclui words[1] até words[3].It includes words[1] through words[3]. O elemento words[4] não está no intervalo.The element words[4] isn't in the range.

var quickBrownFox = words[1..4];

O código a seguir cria um subintervalo com "lazy" e "dog".The following code creates a subrange with "lazy" and "dog". Ele inclui words[^2] e words[^1].It includes words[^2] and words[^1]. O índice final words[^0] não está incluído:The end index words[^0] isn't included:

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

Os exemplos a seguir criam intervalos abertos para o início, fim ou ambos: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"

Você também pode declarar intervalos como variáveis:You can also declare ranges as variables:

Range phrase = 1..4;

Em seguida, o intervalo pode ser usado dentro dos caracteres [ e ]:The range can then be used inside the [ and ] characters:

var text = words[phrase];

Não apenas as matrizes dão suporte a índices e intervalos.Not only arrays support indices and ranges. Você também pode usar índices e intervalos com cadeia de caracteres, Span<T> ou ReadOnlySpan<T> .You can also use indices and ranges with string, Span<T>, or ReadOnlySpan<T>. Para obter mais informações, consulte suporte de tipo para índices e intervalos.For more information, see Type support for indices and ranges.

Você pode explorar mais sobre índices e intervalos do tutorial sobre índices e intervalos.You can explore more about indices and ranges in the tutorial on indices and ranges.

Atribuição de União nulaNull-coalescing assignment

O C# 8,0 apresenta o operador de atribuição de União nula ??= .C# 8.0 introduces the null-coalescing assignment operator ??=. Você pode usar o ??= operador para atribuir o valor do seu operando à direita para seu operando à esquerda somente se o operando esquerdo for avaliado como 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

Para obter mais informações, consulte ?? e?? = artigo de operadores.For more information, see the ?? and ??= operators article.

Tipos construídos não gerenciadosUnmanaged constructed types

No C# 7,3 e anterior, um tipo construído (um tipo que inclui pelo menos um argumento de tipo) não pode ser um tipo não gerenciado.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 partir do C# 8,0, um tipo de valor construído não será gerenciado se ele contiver campos apenas de tipos não gerenciados.Starting with C# 8.0, a constructed value type is unmanaged if it contains fields of unmanaged types only.

Por exemplo, dada a seguinte definição do tipo genérico Coords<T> :For example, given the following definition of the generic Coords<T> type:

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

o Coords<int> tipo é um tipo não gerenciado em C# 8,0 e posterior.the Coords<int> type is an unmanaged type in C# 8.0 and later. Como para qualquer tipo não gerenciado, você pode criar um ponteiro para uma variável desse tipo ou alocar um bloco de memória na pilha para instâncias desse 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 }
};

Para obter mais informações, consulte tipos não gerenciados.For more information, see Unmanaged types.

Stackalloc em expressões aninhadasStackalloc in nested expressions

A partir do C# 8,0, se o resultado de uma expressão stackalloc for do System.Span<T> System.ReadOnlySpan<T> tipo ou, você poderá usar a stackalloc expressão em outras expressões: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

Aprimoramento de cadeias de caracteres idênticas interpoladasEnhancement of interpolated verbatim strings

A ordem dos $ @ tokens e nas cadeias de caracteres idênticas interpoladas pode ser any: $@"..." e @$"..." são cadeias de caracteres idênticas interpoladas válidas.Order of the $ and @ tokens in interpolated verbatim strings can be any: both $@"..." and @$"..." are valid interpolated verbatim strings. Em versões anteriores do C#, o $ token deve aparecer antes do @ token.In earlier C# versions, the $ token must appear before the @ token.