.NET 9.0  -  Novidades do C# 13


   Neste artigo vou apresentar as principais novidades do C# 13 que já estão consolidadas com o lançamento da versão do .NET 9.0 SDK.

Para experimentar esses recursos em primeira mão, você precisará da versão mais recente do Visual Studio 2022 ou do .NET 9 SDK. Ambas as opções fornecem acesso às funcionalidades de ponta do C# 13.

1- Coleções de parâmetros aprimoradas

A palavra-chave params era anteriormente restrita a arrays; agora, ela abrange uma gama mais ampla de tipos de coleção.

Você pode usá-la com System.Span<T>, System.ReadOnlySpan<T> e coleções que implementam System.Collections.Generic.IEnumerable<T> e possuem um método Add.

Além disso, interfaces como System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IReadOnlyCollection<T> e mais podem ser utilizadas com params.

Essa flexibilidade simplifica a passagem de parâmetros para vários cenários :

// Antes
static void ContadorNumeros(params int[] numeros) => Console.WriteLine(numeros.Length);

// .NET 9
static void ContadorNumeros(params ReadOnlySpan<int> numeros) => Console.WriteLine(numeros.Length);
static void ContadorNumeros(params IEnumerable<int> numeros) =>Console.WriteLine(numeros.Count());
static void ContadorNumeros(params HashSet<int> numeros) =>Console.WriteLine(numeros.Count);

Suporte aprimorado para ReadOnlySpan<T>

No C# 13, o suporte para ReadOnlySpan<T> foi aprimorado para permitir que expressões de coleção funcionem diretamente com essa estrutura de alto desempenho.

ReadOnlySpan<T> é uma representação somente leitura segura para tipo e memória de uma região contígua de memória arbitrária. Ela é alocada na pilha e nunca pode escapar para o heap gerenciado, o que ajuda a evitar alocações e melhora o desempenho.

Expressões de coleção agora podem funcionar diretamente com ReadOnlySpan<T>, uma estrutura de alto desempenho que evita alocações. Esse aprimoramento é particularmente benéfico para aplicativos que exigem desempenho ideal.

Benefícios do ReadOnlySpan<T>:

Evita alocações: Como o ReadOnlySpan<T> é uma estrutura alocada na pilha, ele evita alocações de heap, o que pode beneficiar aplicativos de desempenho crítico.

Segurança da memória: Ele fornece uma maneira segura de manipular a memória, garantindo que você não modifique acidentalmente os dados subjacentes.

Versatilidade: O ReadOnlySpan<T> pode apontar para memória gerenciada, memória nativa ou memória gerenciada na pilha, tornando-o versátil para vários cenários.

Considere um cenário em que você precisa inicializar uma coleção de forma eficiente.

public void AddScores(params int[] scores)
{
    var scoresCollection = new int[] { 75, 88, 92 };
    AddScores(scoresCollection);
}

No C# 13, você pode conseguir isso com melhor desempenho usando ReadOnlySpan<T>.

public void AddScores(ReadOnlySpan<int> scores)
{
    foreach (var score in scores)
    {
        // Processa o placar sem alocações
    }
}

Isso é útil para aplicativos que precisam de desempenho ideal, como sistemas em tempo real, desenvolvimento de jogos e aplicativos de negociação de alta frequência.

Suporte aprimorado para IEnumerable<T>

A palavra-chave params foi aprimorada para funcionar com IEnumerable<T>, permitindo que você passe coleções diretamente para métodos que aceitam um número variável de argumentos. Esse aprimoramento melhora a flexibilidade e a usabilidade da palavra-chave params.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        // usando params com IEnumerable<T>.
        AddItems(new List<int> { 1, 2, 3, 4, 5 });
    }

    // Método aceitando params com IEnumerable<T>.
    public static void AddItems(params IEnumerable<int>[] collections)
    {
        foreach (var collection in collections)
        {
            foreach (var item in collection)
            {
                Console.WriteLine(item);
            }
        }
    }
}

Essa prática melhora o desempenho, especialmente em ambientes que exigem alta taxa de requisições, como ASP.NET Core.

2- Sincronização de thread modernizada com lock

O C# 13 apresenta o tipo System.Threading.Lock, projetado para melhorar as práticas de sincronização de thread. Ele ostenta uma API superior em comparação à abordagem tradicional System.Threading.Monitor.

Principais recursos

Escopo de execução exclusivo: o método Lock.EnterScope() estabelece um escopo de execução exclusivo. Isso garante que apenas uma thread execute o código dentro do escopo por vez.

Padrão Dispose of: a estrutura ref retornada de Lock.EnterScope() oferece suporte ao padrão Dispose(), permitindo uma saída elegante do escopo. Isso garante que o bloqueio seja liberado mesmo se ocorrer uma exceção.

Integração com a instrução lock: a instrução lock do C# agora reconhece quando o alvo é um objeto de bloqueio e usa a API atualizada. Essa integração simplifica o código e melhora a segurança da thread.

Benefícios

Segurança de thread aprimorada: ao usar o tipo System.Threading.Lock, os desenvolvedores podem obter melhor sincronização e evitar armadilhas comuns associadas à contenção de thread.

Manutenção do código: a nova API torna o código mais legível e fácil de manter, reduzindo a complexidade da sincronização de threads.

Exemplo:

// Antes
public class LockExemplo
{
  private readonly object _lock = new();

  public void DoStuff() 
  {
    lock (_lock)
    {
        Console.WriteLine("Estamos usando o lock antigo);
    } 
  }
}
// .NET 9 com C# 13
public class LockExemplo 
{
  private readonly Lock _lock = new();

  public void DoStuff() 
  {
    lock (_lock)
    {
        Console.WriteLine("Estamos usando o lock do .NET 9");
    } 
  }
}

3- Propriedades automáticas com lógica personalizada

As propriedades automáticas no C# 13 fornecem uma maneira mais simplificada de declarar propriedades sem campos de apoio explícitos.

No entanto, se você quisesse adicionar lógica personalizada ao getter ou setter, teria que usar a sintaxe de propriedade completa, o que resultava em código boilerplate adicional.

O C# 13 introduziu melhorias que permitem que a lógica personalizada seja incluída diretamente nos getters e setters das propriedades automáticas, reduzindo assim a necessidade da sintaxe de propriedade completa e mantendo o código mais conciso.

Vamos explicar isso com o exemplo de código a seguir. Considere um cenário em que você deseja garantir que uma propriedade de data seja sempre definida para a data atual se o valor fornecido estiver no passado.

public class Program
{
    public static void Main()
    {
        Evento meuEvento = new Evento();
        // Definindo a data passada
        meuEvento.EventoData = new DateTime(2020, 1, 1);
        Console.WriteLine(meuEvento.EventoData); // Exibe a data atual
        // Definindo a data futura
        meuEvento.EventoData = new DateTime(2025, 1, 1);
        Console.WriteLine(meuEvento.EventoData); // Exibe 2025-01-01
    }
}
public class Evento
{
    private DateTime eventoData;
    public DateTime EventoData
    {
        get => eventoData;
        set => eventoData = value < DateTime.Now ? DateTime.Now : value;
    }
}

Com o recurso das propriedades automática aprimoradas, agora você pode incorporar lógica personalizada diretamente na própria definição de propriedade, minimizando a necessidade de campos de apoio e garantindo que seu código permaneça conciso e inteligível.

4-  Nova sequência de escape para o caractere ESCAPE

O C# 13 introduz uma maneira mais conveniente de representar o caractere ESCAPE (Unicode U+001B) dentro de literais de caracteres.

Este novo recurso permite que os desenvolvedores usem a sequência de escape \e em vez dos métodos mais antigos, \u001B ou \x1B. Este aprimoramento simplifica a legibilidade do código e reduz erros potenciais associados a interpretações hexadecimais.

Antes do C# 13, representar o caractere ESCAPE exigia usar a sequência de escape Unicode \u001B ou a sequência de escape hexadecimal \x1B. Esses métodos poderiam ser menos legíveis e mais propensos a erros, especialmente se os caracteres a seguir fossem dígitos hexadecimais válidos.

//Antes
char escapeChar1 = '\u001B'; // Usando a sequencia escape Unicode
char escapeChar2 = '\x1B';   // Usando a sequencia escape hexadecimal
//.NET 9.0
char escapeChar = '\e'; //Usando a nova sequencia escape
 

Benefícios :

Melhor legibilidade: A sequência de escape \e é mais concisa e fácil de ler em comparação com \u001B ou \x1B.

Erros reduzidos: Usar \e minimiza o risco de erros que podem ocorrer com interpretações hexadecimais, onde caracteres subsequentes podem ser mal interpretados como parte da sequência de escape.

Consistência: A nova sequência de escape se alinha com outras sequências de escape comuns, tornando o código mais consistente e fácil de entender.

5- Operador de índice implícito em inicializadores de objetos

O C# 13 permite usar o operador de índice implícito "do fim" (^) dentro de expressões de inicializador de objetos. Isso permite que você inicialize matrizes diretamente do fim, ou seja, agora você pode usar ^ para especificar uma posição em uma coleção que seja relativa ao fim da coleção.

Por exemplo, considere o seguinte exemplo:

var arr = new InitializerDemo
{
    Inteiros =
    {
       [0] = 100,
       [^1] = 1000
    }
};
Console.WriteLine("O valor de arr.Inteiros[0] é {0} e arr.Inteiros[4] é {1}", arr.Inteiros[0], arr.Inteiros[4]);
class InitializerDemo
{
    public int[] Inteiros { get; set; } = new int[5];
}

Ao executar o programa acima, arr.Inteiros[0] terá o valor 100, enquanto arr.Inteiros[4] terá o valor 1000.

O C # 13 agora permite usar o operador de índice implícito “from the end” (^) dentro de expressões inicializadoras de objetos. Isso permite que você inicialize arrays diretamente do fim conforme o exemplo acima.

ere a seguinte classe.

6- Propriedades parciais

As Propriedades parciais, como métodos parciais, são um novo recurso adicionado em C# 13. Eles suportam geradores de fonte e são usados ​​para separar a declaração de uma propriedade de seu código de implementação. Observe que métodos parciais foram introduzidos anteriormente e ganharam alguma força no C# 9.

Você pode declarar propriedades parciais e indexadores parciais no C# 13.

Propriedades parciais e indexadores geralmente seguem as mesmas regras que métodos parciais: você cria uma declaração de declaração e uma declaração de implementação.

As assinaturas das duas declarações devem corresponder. Uma restrição é que você não pode usar uma declaração de propriedade automática para uma propriedade parcial.

Propriedades que não declaram um corpo são consideradas a declaração de declaração.

partial class MinhaClasse
{
    string meuCampo;

    public partial string MinhaPropriedade { get; set; }

    public partial string MinhaPropriedade
    { 
        get => meuCampo; 
        set => meuCampo = value; 
    }
}

Deve-se notar que quando você declara uma propriedade parcial com acessadores tendo corpos de ponto e vírgula sem nenhum código de implementação dentro, presume-se que seja uma declaração de definição.

Por outro lado, quando uma propriedade parcial tem acessadores que contêm código de implementação, ela é considerada uma declaração de implementação.

As Propriedades parciais são usadas para fornecer suporte para geradores de fonte. Seu único propósito é isolar a declaração de uma propriedade de seu código de implementação.

Os novos recursos e aprimoramentos no C# 13 descritos neste artigo darão a você mais flexibilidade e ajudarão você a escrever um código C# mais limpo e sustentável.

Para explorar os novos recursos do C# 13, você deve baixar a versão mais recente do Visual Studio 2022 com .NET 9.

E estamos conversados...

"Ensina-me, Senhor, o teu caminho, e andarei na tua verdade; une o meu coração ao temor do teu nome."
Salmos 86:11

Referências:


José Carlos Macoratti