Bem-vindo ao C# 10
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
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