Featured image of post Como usar recursos do C# 11 em .NET 6 ou versões mais antigas (até mesmo .NET Framework 2.0)

Como usar recursos do C# 11 em .NET 6 ou versões mais antigas (até mesmo .NET Framework 2.0)

C# 11 no AWS Lambda e plugins Dataverse

Introdução Link to this section

A cada lançamento, o C# adiciona recursos que nos ajudam a tornar nossos códigos mais limpos, mais legíveis e mais fáceis de manter. O problema é que, como alguns recursos dependem de implementações de tempo de execução, as versões do C# geralmente estão vinculadas às versões do tempo de execução do .NET. Por exemplo, o C# 11 é habilitado apenas no .NET 7 e superior.

Neste post, mostrarei como usar o C# 11 em versões de tempo de execução mais antigas (até mesmo .NET Framework 2.0).

Por que não atualizar a versão do .NET? Link to this section

Atualizar para a versão mais recente do .NET é a melhor opção. Não apenas nos beneficiamos de novos recursos do C#, mas também de melhorias de desempenho e segurança.

Mas existem alguns cenários em que a atualização não é uma opção devido à compatibilidade ou porque o custo da atualização seria muito alto.

Alguns exemplos são:

  • Funções AWS Lambda em execução no .NET 6 (AWS Lambda não oferece suporte a .NET 7 no momento deste post);
  • Plugins ou extensões para software proprietário, como plugins Dynamics/Dataverse que não são compatíveis com .NET Core
  • Sistemas legados com uma grande base de código que ainda recebem atualizações frequentes.

Quais recursos do C# 11 podem ser usados? Link to this section

Os recursos do C# são divididos em recursos que exigem suporte de tempo de execução e recursos que são apenas açúcar sintático.

Recursos que exigem suporte de tempo de execução não podem ser usados em versões .NET mais antigas, mas a maioria dos recursos que são açúcar sintático são compilados para IL (Linguagem Intermediária .NET) e interpretados por versões .NET mais antigas em tempo de execução (até mesmo .NET Framework 2.0), dependendo apenas de uma versão atualizada do Roslyn (o compilador .NET) para funcionar.

Como usar recursos do C# 11 no .NET 6 e versões anteriores Link to this section

Alguns recursos funcionarão apenas instalando o SDK do .NET 7 e adicionando (ou atualizando) a tag LangVersion para 11 no arquivo csproj.

<LangVersion>11</LangVersion>

Exemplos Link to this section

Aqui estão alguns exemplos dos recursos mais úteis das versões mais recentes do C# (não apenas C# 11).

Instruções de nível superior Link to this section

Não há necessidade de static void Main:

1
2
3
4
5
using System;

Console.WriteLine("Hello World");

Console.ReadKey();

Tipos de referência anuláveis Link to this section

Este é um exemplo funcional de tipos de referência anuláveis e instruções de nível superior no .NET Framework 2.0:

1
2
3
4
5
6
7
8
9
#nullable enable

using System;

string? nullHere = null;

Console.WriteLine($"Length: {nullHere?.Length}");

Console.ReadKey();

💡 Podemos até tratar avisos como erros, como expliquei neste post.

ℹ️ Se o projeto não usar o novo formato csproj, o <Nullable>enable</Nullable> não será interpretado e o uso da diretiva #nullable enable no início de cada arquivo é obrigatório.

⚠️ Uma ressalva de usar tipos anuláveis em versões .NET mais antigas é que as funções do framework não nos informarão se retornam tipos de referência anuláveis porque essas alterações foram implementadas apenas nas versões .NET mais recentes.

EDIT: Um leitor entrou em contato comigo sobre este pacote legal que resolve parcialmente este problema injetando anotações de tipo de referência anulável nos métodos CLR de algumas assemblies (verifique os documentos para mais detalhes): ReferenceAssemblyAnnotator.

Correspondência de padrões Link to this section

A correspondência de padrões também funciona no .NET Framework 2.0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;

Console.WriteLine($"Enter the water temperature in Fahrenheit:");

var isNumber = int.TryParse(Console.ReadLine(), out var number);

string GetWaterState(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        (> 32) and (< 212) => "liquid",
        < 32 => "solid",
        > 212 => "gas",
        32 => "solid/liquid transition",
        212 => "liquid / gas transition"
    };

if (isNumber)
{
    var waterState = GetWaterState(number);

    Console.WriteLine(waterState);
}
else
{
    Console.WriteLine("Invalid number");
}

Console.ReadKey();

⚠️ A correspondência de padrões de lista não funcionará apenas alterando a tag LangVersion. Ele precisa de tipos específicos que explicarei na próxima seção.

Recursos que precisam de tipos específicos Link to this section

Mesmo para recursos que são açúcar sintático, alguns dependem de tipos e atributos implementados nos CLRs mais recentes (por exemplo, a correspondência de padrões de lista e a palavra-chave required).

Se copiarmos esses tipos do código-fonte CLR ou referenciá-los de pacotes NuGet, a compilação será bem-sucedida e os recursos estarão disponíveis.

Mas existe uma alternativa melhor…

Apresentando PolySharp Link to this section

PolySharp é um pacote NuGet criado por Sergio Pedri, Engenheiro de Software da Microsoft, que gera polyfills para esses tipos em tempo de compilação, apenas para os recursos que estão sendo usados no código e que não estão presentes no tempo de execução de destino.

Recursos habilitados pelo PolySharp Link to this section

Esta é uma lista abreviada de alguns recursos C# habilitados pelo PolySharp:

  • Atributos de nulidade
  • Índice e Intervalo
  • Correspondência de padrões de lista
  • Membros obrigatórios
  • Propriedades somente init
  • [CallerArgumentExpression]
  • [StringSyntax]

Para instalá-lo, basta adicionar seu pacote NuGet:

Install-Package PolySharp

⚠️ Como o PolySharp usa geradores de origem, ele não funciona com o arquivo package.config, conforme declarado nesta issue. A issue diz que precisamos usar o .csproj estilo SDK, mas apenas mudar de package.config para Package Reference funcionou para mim.

No Visual Studio, clique com o botão direito do mouse em References e selecione Migrate package.config to Package Reference, confirme as alterações e terminamos: Migrar package.config para Package Reference

Palavra-chave Required, propriedades somente init e registros Link to this section

Aqui está um exemplo de um aplicativo .NET Framework 4.7.2 usando a palavra-chave required, propriedades somente init e registros:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#nullable enable

using System;

var person = new Person() { FirstName= "Sherlock", LastName = "Holmes" };

var address = new Address("Baker Street 221b", "London");

Console.WriteLine($"Person: {person.FirstName} {person.LastName}");
Console.WriteLine($"Address: {address}");

Console.ReadKey();

record Address (string StreetName, string City);

class Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
}

Se inspecionarmos o RequiredMemberAttribute e IsExternalInit, podemos ver que eles foram gerados pelo PolySharp:

⚠️ Os tipos de registro exigem tempos de execução .NET Framework 4 ou superiores.

Código fonte dos exemplos Link to this section

https://github.com/dgenezini/CSharpNewestFeatures

💬 Like or have something to add? Leave a comment below.
Ko-fi
GitHub Sponsor
Licensed under CC BY-NC-SA 4.0
Criado com Hugo
Tema Stack desenvolvido por Jimmy