Hoje, estamos felizes em anunciar o lançamento do C# 10 como parte do .NET 6 e Visual Studio 2022. Neste post, estamos cobrindo um monte dos novos recursos C# 10 que tornam seu código mais bonito, mais expressivo e mais rápido.

Leia o anúncio do Visual Studio 2022 e o anuncio do .NET 6 para saber mais, incluindo como instalar.

Usos globais e implícitos

using diretivas simplificam como você trabalha com namespaces. C# 10 inclui uma nova diretiva e usos implícitos para reduzir o número de usos que você precisa especificar na parte superior de cada arquivo.global using

Global usando diretivas

Se a palavra-chave aparecer antes de uma diretiva, esse uso se aplica a todo o projeto:globalusing

global using System;

Você pode usar qualquer recurso dentro de uma diretiva. Por exemplo, adicionar importações um tipo e disponibilizar os membros do tipo e tipos aninhados ao longo do seu projeto. Se você usar um pseudônimo em sua diretiva de uso, esse pseudônimo também afetará todo o seu projeto:usingglobal usingstatic

global using static System.Console;
global using Env = System.Environment;

Você pode colocar usos globais em qualquer arquivo, incluindo ou um arquivo especificamente nomeado como . O escopo dos usos globais é a compilação atual, que geralmente corresponde ao projeto atual..csProgram.csglobalusings.cs

Para obter mais informações, consulte global usando diretivas.

Usos implícitos

O recurso de uso implícito adiciona automaticamente diretivas comuns para o tipo de projeto que você está construindo. Para habilitar usos implícitos, defina a propriedade em seu arquivo:global usingImplicitUsings.csproj

<PropertyGroup>
    <!-- Other properties like OutputType and TargetFramework -->
    <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Os usos implícitos estão ativados nos novos modelos .NET 6. Leia mais sobre as alterações nos modelos .NET 6 neste post no blog.

O conjunto específico de diretivas incluídas depende do tipo de aplicação que você está construindo. Por exemplo, os usos implícitos para um aplicativo de console ou uma biblioteca de classe são diferentes daqueles para um aplicativo ASP.NET.global using

Para obter mais informações, consulte este artigo de uso implícito.

Combinando recursos

Diretivas tradicionais no topo de seus arquivos, diretivas globais e usos implícitos funcionam bem juntos. Os usos implícitos permitem que você inclua os namespaces .NET apropriados para o tipo de projeto que você está construindo com uma única linha em seu arquivo de projeto. as diretivas permitem que você inclua espaços de nome adicionais para torná-los disponíveis ao longo de seu projeto. As diretivas na parte superior de seus arquivos de código permitem que você inclua espaços de nome usados por apenas alguns arquivos em seu projeto.usingusingglobal usingusing

Independentemente de como são definidos, diretivas extras aumentam a possibilidade de ambiguidade na resolução de nomes. Se você encontrar isso, considere adicionar um pseudônimo ou reduzir o número de namespaces que você está importando. Por exemplo, você pode substituir diretivas por diretivas explícitas usando diretivas no topo de um subconjunto de arquivos.usingglobal using

Se você precisar remover namespaces que foram incluídos através de usos implícitos, você pode especificá-los em seu arquivo de projeto:

<ItemGroup>
  <Using Remove="System.Threading.Tasks" />
</ItemGroup>

Você também pode adicionar namespace que se comportam como se fossem diretivas, você pode adicionar itens ao seu arquivo de projeto, por exemplo:global usingUsing

<ItemGroup>
  <Using Include="System.IO.Pipes" />
</ItemGroup>

Namespace com escopo de arquivo

Muitos arquivos contêm código para um único namespace. A partir de C# 10, você pode incluir um namespace como uma declaração, seguido por um ponto e sem os suportes encaracolados:

namespace MyCompany.MyNamespace;

class MyClass // Note: no indentation
{ ... } 

Isso simplifica o código e remove um nível de aninhamento. Apenas uma declaração de namespace com escopo de arquivo é permitida e deve vir antes que qualquer tipo seja declarado.

Para obter mais informações sobre namespaces com escopo de arquivo, consulte o artigo da palavra-chave namespace.

Melhorias para expressões lambda e grupos de métodos

Fizemos várias melhorias tanto para os tipos quanto para a sintaxe em torno de lambdas. Esperamos que estes sejam amplamente úteis, e um dos cenários de condução tem sido tornar ASP.NET APIs mínimas ainda mais simples.

Tipos naturais para lambdas

Expressões lambda agora às vezes têm um tipo “natural”. Isso significa que o compilador pode muitas vezes inferir o tipo de expressão lambda.

Até agora, uma expressão lambda tinha que ser convertida em um delegado ou um tipo de expressão. Para a maioria dos propósitos, você usaria um dos tipos sobrecarregados ou delegados no BCL:Func<…>Action<…>

Func<string, int> parse = (string s) => int.Parse(s);

Começando com C# 10, no entanto, se uma lambda não tiver um tipo de alvo, tentaremos calcular um para você:

var parse = (string s) => int.Parse(s);

Você pode passar o mouse no seu editor favorito e ver que o tipo ainda está . Em geral, o compilador usará um disponível ou delegar, se existir um adequado. Caso contrário, ele sintetizará um tipo de delegado (por exemplo, quando você tem parâmetros ou tem um grande número de parâmetros).var parseFunc<string, int>FuncActionref

Nem todas as lambdas têm tipos naturais – alguns simplesmente não têm informações de tipo suficiente. Por exemplo, deixar de fora os tipos de parâmetros deixará o compilador incapaz de decidir qual tipo de delegado usar:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

O tipo natural de lambdas significa que eles podem ser atribuídos a um tipo mais fraco, como ou:objectDelegate

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

Quando se trata de árvores de expressão, fazemos uma combinação de digitação “alvo” e “natural”. Se o tipo de destino for ou não genérico (tipo base para todas as árvores de expressão) e a lambda tiver um tipo de delegado natural, em vez disso produziremos um :LambdaExpressionExpressionDExpression

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s);       // Expression<Func<string, int>>

Tipos naturais para grupos de métodos

Grupos de métodos (ou seja, nomes de métodos sem listas de argumentos) agora também às vezes também têm um tipo natural. Você sempre foi capaz de converter um grupo de método para um tipo de delegado compatível:

 Func<int> read = Console.Read;
Action<string> write = Console.Write;

Agora, se o grupo de método tem apenas uma sobrecarga, ele terá um tipo natural:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

Tipos de retorno para lambdas

Nos exemplos anteriores, o tipo de retorno da expressão lambda era óbvio e estava apenas sendo inferido. Nem sempre é assim:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

Em C# 10, você pode especificar um tipo de retorno explícito em uma expressão lambda, assim como você faz em um método ou uma função local. O tipo de retorno vai bem antes dos parâmetros. Quando você especifica um tipo de retorno explícito, os parâmetros devem ser entre parênteses, de modo que não seja muito confuso para o compilador ou outros desenvolvedores:

var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

Atributos em lambdas

A partir de C# 10, você pode colocar atributos nas expressões lambda da mesma forma que você faz para métodos e funções locais. Eles vão exatamente onde você espera; no início. Mais uma vez, a lista de parâmetros da lambda deve ser parêntese quando houver atributos:

 Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";

Assim como as funções locais, atributos podem ser aplicados a lambdas se forem válidos .AttributeTargets.Method

Lambdas são invocadas de forma diferente dos métodos e funções locais, e como resultado os atributos não têm qualquer efeito quando a lambda é invocada. No entanto, atributos sobre lambdas ainda são úteis para análise de código, e também são emitidos sobre os métodos que o compilador gera sob o capô para lambdas, para que possam ser descobertos através de reflexão.

Melhorias nas estruturas

C# 10 introduz recursos para estruturas que proporcionam melhor paridade entre estruturas e classes. Essas novas características incluem construtores sem parâmetros, iniciadores de campo, estruturas de registro e expressões.with

Construtores de estrutura sem parâmetros e iniciadores de campo

Antes do C# 10, cada estrutura tinha um construtor implícito sem parâmetros público que definia os campos da estrutura para . Foi um erro você criar um construtor sem parâmetros em uma estrutura.default

A partir de C# 10, você pode incluir seus próprios construtores de estrutura sem parâmetros. Se você não fornecer um, o construtor implícito sem parâmetros será fornecido para definir todos os campos à sua inadimplência. Construtores sem parâmetros que você cria em estruturas devem ser públicos e não podem ser parciais:

 public struct Address
{
    public Address()
    {
        City = "<unknown>";
    }
    public string City { get; init; }
}

Você pode inicializar campos em um construtor sem parâmetros como acima, ou você pode inicializá-los através de iniciadores de campo ou propriedades:

public struct Address
{
    public string City { get; init; } = "<unknown>";
}

As estruturas criadas via ou como parte da alocação de array ignoram construtores sem parâmetros explícitos e sempre definem os membros de estrutura para seus valores padrão. Para obter mais informações sobre construtores sem parâmetros em estruturas, consulte o tipo de estrutura .default

estruturas de registro

A partir do C# 10, os registros já podem ser definidos com . Estes são semelhantes às classes de registro que foram introduzidas em C# 9:record struct

public record struct Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

Você pode continuar a definir classes de registro com , ou você pode usar para clareza.recordrecord class

As estruturas já tinham igualdade de valor – quando você as compara é por valor. Estruturas de registro adicionam suporte e o operador. As estruturas de registro fornecem uma implementação personalizada para evitar os problemas de desempenho de reflexão, e incluem recursos de gravação como uma substituição.IEquatable==IEquatableToString()

As estruturas de registro podem ser posicionais, com um construtor primário declarando implicitamente membros públicos:

public record struct Person(string FirstName, string LastName);

Os parâmetros da construtora primária tornam-se propriedades públicas auto-implementadas da estrutura de registro. Ao contrário das classes de registro, as propriedades criadas implicitamente são lidas/escritas. Isso torna mais fácil converter tuplas em tipos nomeados. Alterar os tipos de retorno de uma tupla gosta de um tipo de tipo de pode limpar seu código e garantir nomes de membros consistentes. Declarar a estrutura de registro posicional é fácil e mantém a semântica mutável.(string FirstName, string LastName)Person

Se você declarar uma propriedade ou campo com o mesmo nome de um parâmetro de construção principal, nenhuma propriedade automática será sintetizada e a sua será usada.

Para criar uma estrutura de registro imutável, adicione à estrutura (como puder a qualquer estrutura) ou aplique-se a propriedades individuais. Os iniciadores de objetos fazem parte da fase de construção, onde apenas as propriedades de leitura podem ser definidas. Aqui está apenas uma das maneiras que você pode trabalhar com estruturas de registro imutáveis:readonlyreadonly

var person = new Person { FirstName = "Mads", LastName = "Torgersen"};

public readonly record struct Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

Saiba mais sobre as estruturas de registros neste artigo.

sealed modificador em classes de registroToString()

As aulas de registro também foram melhoradas. A partir de C# 10, o método pode incluir o modificador selado, o que impede o compilador de sintetizar uma implementação para quaisquer registros derivados.ToString()ToString

Saiba mais sobre os registros deste artigo .ToString()

with expressões sobre estruturas e tipos anônimos

C# 10 suporta expressões para todas as estruturas, incluindo estruturas de registro, bem como para tipos anônimos: with

var person2 = person with { LastName = "Kristensen" };

Isso retorna uma nova instância com o novo valor. Você pode atualizar qualquer número de valores. Os valores que você não definiu manterão o mesmo valor da instância inicial.

Saiba mais sobre with nesse artigo

Melhorias de interpolação de string

Quando adicionamos strings interpoladas ao C#, sempre sentimos que havia mais que poderia ser feito com essa sintaxe abaixo da linha, tanto para desempenho quanto para expressividade. Com C# 10, chegou a hora!

Manipuladores de interpolação de string

Hoje o compilador transforma strings interpoladas em uma chamada para. Isso pode levar a uma série de alocações – o boxe de argumentos, a alocação de uma matriz de argumentos e, claro, a própria sequência resultante. Além disso, não deixa espaço no significado da interpolação real. string.Format

Em C# 10 adicionamos um padrão de biblioteca que permite que uma API “assuma” o manuseio de uma expressão interpolada de argumento de sequência de strings. Como exemplo, considere: StringBuilder.Append

var sb = new StringBuilder();
sb.Append($"Hello {args[0]}, how are you?");

Até agora, isso chamaria a sobrecarga com uma sequência recém-alocada e calculada, anexando isso ao em um pedaço. No entanto, agora tem uma nova sobrecarga que tem precedência sobre a sobrecarga de strings quando uma sequência interpolada é usada como argumento.Append(string? value)StringBuilderAppendAppend(ref StringBuilder.AppendInterpolatedStringHandler handler)

Em geral, quando você vê tipos de parâmetros da forma que o autor da API tem feito alguns trabalhos nos bastidores para lidar com cordas interpoladas mais adequadamente para seus propósitos. No caso do nosso exemplo, as strings , e serão individualmente anexadas ao , que é muito mais eficiente e tem o mesmo resultado.SomethingInterpolatedStringHandlerAppend”Hello “args[0]”, how are you?”StringBuilder

Às vezes você quer fazer o trabalho de construir a corda apenas sob certas condições. Um exemplo é: Debug.Assert

Debug.Assert(condition, $"{SomethingExpensiveHappensHere()}");

Na maioria dos casos, a condição será verdadeira e o segundo parâmetro não é uso. No entanto, todos os argumentos são computados em cada chamada, desnecessariamente retardando a execução. agora tem uma sobrecarga com um construtor de cordas interpolado personalizado, o que garante que o segundo argumento não seja sequer avaliado a menos que a condição seja falsa.Debug.Assert

Finalmente, aqui está um exemplo de realmente mudar o comportamento da interpolação de sequência em uma determinada chamada: permite especificar o usado para formatar as expressões nos buracos do próprio argumento de sequência interpolada:String.Create()IFormatProvider

String.Create(CultureInfo.InvariantCulture, $"The result is {result}");

Você pode aprender mais sobre manipuladores de string interpolados, neste artigo e neste tutorial sobre a criação de um manipulador personalizado.

Strings interpoladas constantes

Se todos os buracos de uma sequência interpolada são strings constantes, então a sequência resultante agora também é constante. Isso permite que você use a sintaxe de interpolação de strings em mais lugares, como atributos:

[Obsolete($"Call {nameof(Discard)} instead")]

Note que os orifícios devem ser preenchidos com strings constantes. Outros tipos, como valores numéricos ou datas, não podem ser usados porque são sensíveis , e não podem ser computados na hora da compilação. Culture

Outras melhorias

C# 10 tem uma série de melhorias menores em todo a linguagem. Alguns destes só fazem C# trabalhar do jeito que você espera.

Misture declarações e variáveis na desconstrução

Antes do C# 10, a desconstrução exigia que todas as variáveis fossem novas, ou todas elas fossem previamente declaradas. Em C# 10, você pode misturar:

int x2;
int y2;
(x2, y2) = (0, 1);       // Works in C# 9
(var x, var y) = (0, 1); // Works in C# 9
(x2, var y3) = (0, 1);   // Works in C# 10 onwards

Saiba mais no artigo sobre desconstrução.

Atribuição definitiva melhorada

C# produz erros se você usar um valor que não foi definitivamente atribuído. C# 10 entende melhor seu código e produz erros menos espúrios. Essas mesmas melhorias também significam que você verá erros e avisos menos espúrios para referências nulas.

Saiba mais sobre c# atribuição definitiva no que há de novo no artigo C# 10.

Padrões de propriedade estendidos

C# 10 adiciona padrões de propriedade estendidos para facilitar o acesso aos valores de propriedades aninhadas em padrões. Por exemplo, se adicionarmos um endereço ao registro acima, podemos combinar padrão em ambas as maneiras mostradas aqui:Person

object obj = new Person
{
    FirstName = "Kathleen",
    LastName = "Dollard",
    Address = new Address { City = "Seattle" }
};

if (obj is Person { Address: { City: "Seattle" } })
    Console.WriteLine("Seattle");

if (obj is Person { Address.City: "Seattle" }) // Extended property pattern
    Console.WriteLine("Seattle");

O padrão de propriedade estendida simplifica o código e facilita a leitura, especialmente quando se corresponde a várias propriedades.

Saiba mais sobre padrões de propriedade estendidos no artigo de correspondência de padrões.

Atributo de expressão do chamador

CallerArgumentExpressionAttribute fornece informações sobre o contexto de uma chamada de método. Como os outros atributos do CompilerServices, esse atributo é aplicado a um parâmetro opcional. Neste caso, uma sequência:

void CheckExpression(bool condition, 
    [CallerArgumentExpression("condition")] string? message = null )
{
    Console.WriteLine($"Condition: {message}");
}

O nome do parâmetro passado é o nome de um parâmetro diferente. A expressão passou como argumento para esse parâmetro será contida na sequência. Por exemplo CallerArgumentExpression

var a = 6;
var b = true;
CheckExpression(true);
CheckExpression(b);
CheckExpression(a > 5);

// Output:
// Condition: true
// Condition: b
// Condition: a > 5      

Um bom exemplo de como esse atributo pode ser usado é ArgumentNullException.ThrowIfNull(). Ele evita ter que passar no nome do parâmetro, desacum que ele desacumpe do valor fornecido:

void MyMethod(object value)
{
    ArgumentNullException.ThrowIfNull(value);
}

Saiba mais sobre CallerArgumentExpressionAttribute

Recursos de visualização

C# 10 GA inclui membros abstratos estáticos em interfaces como um recurso de pré-visualização. Lançar um recurso de pré-visualização no GA nos permite obter feedback sobre um recurso que levará mais tempo do que uma única versão para criar. Membros abstratos estáticos em interfaces são a base para um novo conjunto de restrições matemáticas genéricas que permitem abstrair sobre quais operadores estão disponíveis. Você pode ler mais sobre restrições matemáticas genéricas neste artigo.

Fechamento

Instale .NET 6 ou Visual Studio 2022, aproveite c# 10 e nos diga o que você acha!

  • Kathleen Dollard (PM para as Línguas .NET) e Mads Torgersen (C# Lead Designer)

Autor: Kathleen Dollard - Principal Gerente de Programa, .NET

:red_circle: Twitter

Artigo Original