Novidades do .NET 5

Em 2019 a Microsoft anunciava que após o lançamento do .NET 3.0, sua próxima evolução seria o .NET 5, uma versão que unifica o .NET Framework com o .NET Core em uma única plataforma.

Agora com apenas um ecossistema podemos direcionar o desenvolvimento de aplicações para Windows, MacOS, Linux, OS, Android, tvOS, watchOS e outras plataformas de uma forma mais transparente.
O.NET 5 não substitui o .NET Framework 4.x, que ainda terá suporte pela Microsoft por algum tempo, mas de agora em diante o .NET 5 será a principal implementação do .NET, pelo menos até a chegada do .NET 6 😎
Mas e você que já usava outra versão do .NET e acabou de instalar o .NET 5, está sentindo falta de algum recurso da plataforma? Ou quer saber de alguma novidade para deixar o desenvolvimento com C# mais dinâmico? Ou ainda, você começou a utilizar o .NET 5 e simplesmente quer saber como deixar seu projeto atualizado com as últimas novidades dele? Então, vamos ao artigo.
C# versão 9
Junto com a plataforma .NET 5 a Microsoft lança o C# 9, hoje a principal linguagem do .NET, que adiciona e aprimora a linguagem com recursos interessantes:
- Um novo tipo: Nessa nova versão da linguagem foi introduzido o tipo
record
, uma estrutura, que permite definir um tipo de referência para encapsular dados e dar suporte a criação de tipos com propriedades imutáveis, além de compará-lo como se fosse um tipo de valor.
No exemplo abaixo, tipamos o objeto Pessoa
como record
...
public record Pessoa{
public string Nome { get; set; }
public string Identificador { get; set; }
public string Email { get; set; }
}
Foram definidos dois objetos Pessoa
que são do tipo record
. Em uma comparação, observamos seus valores, como no código abaixo, em que verificamos a igualdade com o método Equals()
:
Pessoa pessoa1 = new(){
Nome="André Silva",
Identificador="12345678",
Email="[email protected]"
};
Pessoa pessoa2 = new(){
Nome="André Silva",
Identificador="12345678",
Email="[email protected]"
};
Console.WriteLine(Equals(pessoa1,pessoa2)); //true
Para o mesmo exemplo, se alteramos de public record Pessoa
para public class Pessoa
, por ser a classe um tipo de referência, o resultado Console.WriteLine(Equals(pessoa1,pessoa2))
será false
.
Outro recurso interessante na utilização de record
é a expressão with
, muito útil, pois com ela conseguimos criar um objeto com base em outro que já existe. Veja no exemplo abaixo:
Pessoa pessoa1 = new(){
Nome="André Silva",
Identificador="12345678",
Email="[email protected]"
};
Pessoa pessoa2 = pessoa1 with {Nome="Pedro Malazartes"};
Console.WriteLine(pessoa2);
//Pessoa { Nome = Pedro Malazartes, Identificador = 12345678, Email = [email protected] }
Com a expressão with
foi possível criar o objeto pessoa2
alterando somente o valor da propriedade nome
e aproveitar os valores das outras propriedades do objeto pessoa1
.
- Setter somente inicialização: Com o uso da palavra reservada
init
definimos que apenas quando fizermos a inicialização do objeto poderemos atribuir valores às propriedades que servem somente para leitura. No trecho de código seguinte, só poderemos atribuir valores paraNome
,Identificador
eEmail
quando inicializarmosPessoa
.
public record Pessoa{
public string Nome { get; init; }
public string Identificador { get; init; }
public string Email { get; init; }
}
Agora inicializamos as propriedades durante a criação do objeto.
Pessoa p = new Pessoa{
Name="André Silva",
Identificador="12345678",
Email="[email protected]"
};
Se depois da inicialização alterarmos alguma informação recebida pelo objeto, ocorrerá um erro de compilação.

- Código mais enxuto com Instruções de Nível Superior : O C# 9 trouxe como recurso não precisarmos mais escrever explicitamente o método
Main()
em projetos do tipoConsole
.. Veja o código abaixo:
using System;
namespace Novidades
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Novidades do .NET 5");
}
}
}
Todo esse trecho tem o mesmo resultado da simplificação que apresentamos em:
using System;
Console.WriteLine("Novidades do .NET 5");
ou
System.Console.WriteLine("Novidades do .NET 5");
Mas por que este recurso é interessante? Ele permite a escrita de códigos menores e mais simples, o que pode facilitar a aprendizagem da linguagem por iniciantes. É útil também para criação de pequenos utilitários como Azure Functions e GitHub Actions.
Devemos nos atentar, pois para utilizar a instrução de nível superior é importante que a aplicação tenha somente um ponto de entrada. A adição de mais de uma instrução de nível superior no projeto acarreta um erro de compilação CS8802- Somente uma unidade de compilação pode ter instruções de nível superior.
- Simplificando a utilização do
new
: Com objetivo de simplificar ainda mais a escrita de código C#, podemos omitir o tipo da expressãonew
quando já conhecemos o tipo do objeto a ser criado,. Vamos ao exemplo, no qual já sabemos que o objetopessoa1
é do tipoPessoa
e portanto, podemos omitir o tipo denew
:
Pessoa pessoa1 = new(){
Nome="André Silva",
Identificador="12345678",
Email="[email protected]"
};
ou simplesmente:
Pessoa pessoa2 = new();

F# versão 5
O F# é a linguagem funcional da Microsoft e com a chegada do .NET 5.também recebeu uma atualização com a adição do recurso:
- Interpolação de Strings: Agora o F# também possui esse recurso, semelhante à interpolação de cadeias de caracteres que encontramos no C# e no JavaScript. Com ele podemos colocar valores em determinadas posições de uma string sem precisar fazer algum tipo de concatenação. Veja um exemplo:
let usuario = "andresilva"
let mensagem = $" Seja bem-vindo {usuario}."
Atualizações para o Visual Basic
Para pessoas que já trabalham com VB, o .NET 5 não adicionou nenhum novo recurso à linguagem. Contudo, ele trouxe algumas atualizações para dar suporte estendido na utilização de modelos de projetos via CLI.
Descrição | Parâmetro (dotnet new) |
---|---|
Aplicativo do Console | console |
Biblioteca de classes | classlib |
Aplicativo WPF | wpflib |
Biblioteca de controles personalizados wpf | wpfcustomcontrollib |
Windows Aplicativo Forms (WinForms) | winforms |
Windows Biblioteca de classes(WinForms) | winformslib |
Projeto de Teste de Unidade | mstest |
Projeto de Teste NUnit 3 | nunit |
Item de Teste NUnit 3 | nunit-test |
Projeto de teste xUnit | xunit |
Novos recursos para System.Text.Json
O System.Text.Json é o namespace (um conjunto organizado de classes) que a plataforma disponibiliza para trabalhar com serialização e desserialização de JSON, e dentro das novidades do .net 5 esse namespace recebeu uma série de novas opções.
Preservar e manipular referências circulares: Para realizar a manutenção de referências circulares foi definida a propriedade
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get; set; }
que deve ser definida comoPreserve
. Sendo assim:Ao serializar: Para tipos mais complexos, o objeto serializador grava as propriedades $id , $values e $ref (metadados).
Ao desserializar: para o processo de desserialização os metadados são utilizados pelo objeto desserializador em uma tentativa de compreender, contudo os metadados esperados não são obrigatórios de serem passados neste processo.
Vamos ver um exemplo de utilização deste recurso:
...
public class Funcionario
{
public string Nome { get; set; }
public Funcionario Gerente { get; set; }
public List<Funcionario> Relatorio { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
Funcionario silva = new(){Nome = "Andre Silva"};
Funcionario pedro = new(){Nome = "Pedro Parker"};
silva.Relatorio = new List<Funcionario> { pedro };
pedro.Gerente = silva;
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.Preserve,
WriteIndented = true
};
string silvaJson = JsonSerializer.Serialize(silva, options);
Console.WriteLine($"Silva serializado:\n{silvaJson}");
Funcionario silvaJsonDesserializado =
JsonSerializer.Deserialize<Funcionario>(silvaJson, options);
}
}
Como saída teremos o seguinte resultado:
Silva serializado:
{
"$id": "1",
"Nome": "Andre Silva",
"Gerente": null,
"Relatorio": {
"$id": "2",
"$values": [
{
"$id": "3",
"Nome": "Pedro Parker",
"Gerente": {
"$ref": "1"
},
"Relatorio": null
}
]
}
}
Porém, de acordo com a Microsoft este recurso não se aplica para preservar tipos de valor ou tipos imutáveis (strings). A System.Text.Json. para fazer comparações entre objetos, utiliza-se o ReferenceEqualityComparer.Instance
, com o qual a igualdade é entendida por referência, não por valor. Veja um exemplo que dá prosseguimento ao código anterior:
...
Console.WriteLine(silvaJsonDesserializado.Relatorio[0].Gerente==
silvaJsonDesserializado); //true
Para mais detalhes, acesse a documentação oficial.
- Métodos de extensão
HttpClient
eHttpContent
: Como a serialização de objetos Json é uma atividade comum, o .NET trouxe alguns métodos de extensão paraHttpClient
eHttpContent
que permite realizar operações sobre um Json, com apenas uma linha de código. Veja o exempĺo abaixo usandoGetFromJsonAsync
ePostAsJsonAsync
da classeHttpClientJsonExtensions
:
...
public class Usuario
{
public int Id { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
public class Program
{
public static async Task Main()
{
using HttpClient httpClient = new()
{
BaseAddress = new Uri("https://jsonplaceholder.typicode.com")
};
Usuario user = await httpClient.GetFromJsonAsync<Usuario>("users/2");
Console.WriteLine($"Id: {user.Id}");
Console.WriteLine($"Nome: {user.Name}");
Console.WriteLine($"Usuário: {user.Username}");
Console.WriteLine($"Email: {user.Email}");
...
- Gravação de números entre aspas: Como novidade a System.Text.Json adicionou a possibilidade de serializar números entre aspas nos Jsons de entradas utilizando a propriedade
public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get; set; }
. Veja um exemplo:
...
Cliente cliente = new()
{
Nome = "André Silva",
Idade = 30,
Cpf = "111.222.555-33"
};
JsonSerializerOptions options = new()
{
NumberHandling =
JsonNumberHandling.AllowReadingFromString |
JsonNumberHandling.WriteAsString,
WriteIndented = true
};
string clienteJson =
JsonSerializer.Serialize<Cliente>(cliente, options);
Console.WriteLine($"JSON:\n{clienteJson}");
Cliente clienteJsonDesserializado =
JsonSerializer.Deserialize<Cliente>(clienteJson, options);
Console.WriteLine($"Nome: {clienteJsonDesserializado.Nome}");
Console.WriteLine($"Idade: {clienteJsonDesserializado.Idade}");
Console.WriteLine($"CPF: {clienteJsonDesserializado.Cpf}");
...
Serializando o objeto cliente para Json:
JSON:
{
"Nome": "Andre Silva",
"Idade": "30",
"Cpf": "111.222.555-33"
}
Desserializando o objeto de Json para um objeto:
Nome: Andre Silva
Idade: 30
CPF: 111.222.555-33
- Tipos e registros imutáveis: Através de um método construtor público a
System.Text.Json
permite a desserialização de classes ou estruturas imutáveis. Mas precisamos lembrar de que em classes com mais de um construtor deve-se usar o atributo[JsonConstructor]
. Nas classes com um único construtor, será usado o construtor padrão. Veja o código abaixo:
public struct Dependente
{
public string Nome { get; }
public int Idade { get; }
public string Parentesco { get; }
public DateTime DataNascimento { get; }
[JsonConstructor]
public Dependente(DateTime dataNascimento, int idade, string nome, string parentesco) =>
(DataNascimento, Idade, Nome,Parentesco) = (dataNascimento, idade, nome,parentesco);
}
public class Program
{
public static void Main()
{
var json = @"{""nome"":""Penelope"",
""idade"":36,
""parentesco"":""filha"",
""dataNascimento"":""1985-09-06T11:31:01.923395-07:00""} ";
Console.WriteLine($"Input JSON: {json}");
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
var dependenteJsonDesserializado = JsonSerializer.Deserialize<Dependente>(json, options);
Console.WriteLine($"Nome: {dependenteJsonDesserializado.Nome}");
Console.WriteLine($"Idade: {dependenteJsonDesserializado.Idade}");
Console.WriteLine($"Parentesco: {dependenteJsonDesserializado.Parentesco}");
Console.WriteLine($"Data Nasc.: {dependenteJsonDesserializado.DataNascimento}" );
var retornandoParaJson = JsonSerializer.Serialize<Dependente>(dependenteJsonDesserializado, options);
Console.WriteLine($"JSON de Saída: {retornandoParaJson}");
}
}
Como saída da execução deste código teremos:
JSON de Entrada: {"nome":"Penelope","idade":36,"parentesco":"filha","dataNascimento":"1985-09-06T11:31:01.923395-07:00"}
Nome: Penelope
Idade: 36
Parentesco: filha
Data Nasc.: 9/6/1985 3:31:01 PM
JSON de Saída: {"nome":"Penelope","idade":36,"parentesco":"filha","dataNascimento":"1985-09-06T15:31:01.923395-03:00"}
- Inclusão de campos para serialização: Também temos como recurso poder adicionar campos aos processos de serialização e desserialização, por meio do uso do
JsonSerializerOptions.IncludeFields
e do atributo[JsonInclude]
possibilitando informarmos quais campos queremos serializar. No exemplo a seguir, temos uma forma de utilização:
public class Pessoa
{
public string Nome;
public string Email;
public string Cpf;
}
public class Pessoa2
{
[JsonInclude]
public string Nome;
[JsonInclude]
public string Email;
[JsonInclude]
public string Cpf;
}
public class Program
{
public static void Main()
{
var json =
@"{""Nome"":""Andre Silva"",""Email"":""[email protected]"",""Cpf"":""123456-99""} ";
Console.WriteLine($"JSON de entrada: {json}");
var options = new JsonSerializerOptions
{
IncludeFields = true,
};
//Desserializando para objeto
var pessoa = JsonSerializer.Deserialize<Pessoa>(json, options);
Console.WriteLine($"pessoa.Nome: {pessoa.Nome}");
Console.WriteLine($"pessoa.Email: {pessoa.Email}");
Console.WriteLine($"pessoa.Nome {pessoa.Cpf}");
//Serializando
var pessoaJson =
JsonSerializer.Serialize<Pessoa>(pessoa,options);
Console.WriteLine($"JSON de saída: {pessoaJson}");
//Desserializando para objeto pessoa2
var pessoa2 = JsonSerializer.Deserialize<Pessoa2>(json);
Console.WriteLine($"pessoa2.Nome: {pessoa2.Nome}");
Console.WriteLine($pessoa2.Email: {pessoa2.Email}");
Console.WriteLine($"pessoa2.Cpf: {pessoa2.Cpf}");
//Serializando o pessoa2
var pessoa2Json = JsonSerializer.Serialize<Pessoa2>(pessoa2);
Console.WriteLine($"JSON de saída pessoa2: {pessoa2Json}");
}
}
Com a configuração do JsonSerializerOptions
ou da inclusão do [JsonInclude]
, podemos definir quais classes ou campos serão serializados como Json. Como saída da execução do código anterior temos:
JSON de entrada: {"Nome":"Andre Silva","Email":"[email protected]","Cpf":"123456-99"}
pessoa.Nome: Andre Silva
pessoa.Email: [email protected]
pessoa.Nome 123456-99
JSON de saída: {"Nome":"Andre Silva","Email":"[email protected]","Cpf":"123456-99"}
pessoa2.Nome: Andre Silva
pessoa2.Email: [email protected]
pessoa2.Cpf: 123456-99
JSON de saída pessoa2: {"Nome":"Andre Silva","Email":"[email protected]","Cpf":"123456-99"}
- Ignorar propriedades condicionalmente: No processo de serialização de objetos, o padrão é pegar todas as propriedades públicas, porém na adição de recursos ao namespace
System.Text.Json
temos agora a possibilidade de ignorar as propriedades que não devem ser serializadas.- Propriedades individuais: usamos o atributo
[JsonIgnore]
. Exemplo:
- Propriedades individuais: usamos o atributo
public class Usuario
{
public string Nome { get; set; }
public string Email { get; set; }
[JsonIgnore]
public string Login { get; set; }
}
O Json de saída gerado será {"Nome":"Andre Silva","Email":"[email protected]"}
. Para adicionar condições usamos uma estrutura igual a apresentada a seguir:
JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string Login { get; set; }
Para saber quais as condições estão disponíveis, consulte a definição da Enumeração disponível no link oficial da documentação.
- Propriedades somente leitura: Para ignorar as propriedades somente leitura temos a propriedade
JsonSerializerOptions.IgnoreReadOnlyProperties
, vamos ao exemplo:
public class Usuario
{
public string Nome { get; set; }
public string Email { get; set; }
public string Login { get; private set; } ="andre.silva"; //propriedade somente leitura e com valor default.
}
class Program
{
static void Main(string[] args)
{
var options = new JsonSerializerOptions
{
IgnoreReadOnlyProperties = true,
WriteIndented = true
};
Usuario usuario = new(){
Nome="Andre Silva",
Email="[email protected]"
};
var usuarioJson =
JsonSerializer.Serialize<Usuario>(usuario,options);
Console.WriteLine($"JSON de saída: {usuarioJson}");
}
}
Como saída, temos um Json que vai ignorar a propriedade Login
definida como somente leitura, {"Nome":"Andre Silva","Email":"[email protected]"}
.
- Propriedades com valor
null
: Para ignorar as propriedades de valor nulo, usamos a propriedadeDefaultIgnoreCondition
, veja o exemplo:
public class Usuario
{
public string Nome { get; set; }
public string Email { get; set; }
public string Login { get; set; }
}
class Program
{
static void Main(string[] args)
{
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
Usuario usuario = new(){
Nome="Andre Silva",
Email=null,
Login="andre.silva"
};
var usuarioJson =
JsonSerializer.Serialize<Usuario>(usuario,options);
Console.WriteLine($"JSON de saída: {usuarioJson}");
}
}
Como saída teremos o Json {"Nome":"Andre Silva","Login":"andre.silva"}
, ignorando a propriedade de valor null
.
Tecnologias descontinuadas ou sem suporte no .NET 5
Das tecnologias sem suporte no .NET 5 temos o Web Forms
, usado para desenvolver soluções web como se desenvolve para desktop. Para o desenvolvimento web, há algum tempo a Microsoft já recomenda a utilização do ASP. NET MVC, por oferecer maior performance e flexibilidade no uso de diferentes ambientes para desenvolver.
O .NET 5 também não conta mais com o WCF (WIndows Communication Foundation), que definia um modelo para a criação de aplicativos orientados a serviços, com foco nas aplicações distribuídas pela plataforma Windows.
Conclusão
O .NET 5 nasce como um avanço deste produto da Microsoft, com o objetivo de unificar a plataforma .NET como um todo. Com este artigo apresentamos algumas das mudanças que vieram com ele. E aí? O que achou das novidades? Essas opções já estão disponíveis para evoluirmos nossas aplicações baseadas em .NET e criarmos novas soluções multiplataformas.
Agora é esperar o .NET 6. Até a próxima! 😉
Para aprender sobre .NET/C# e mergulhar mais fundo em tecnologia, veja: