Esta publicação está disponível em vídeo, ampliada e revisada, no canal da EximiaCo.
Problemas recorrentes, resolvidos muitas vezes, geralmente têm soluções implementadas de maneira similar. Em implementações de DTOs, somente para leitura, por exemplo, é comum:
- definir construtores onde cada parâmetro corresponde a uma das propriedades do tipo;
- implementar versões customizadas da função de igualdade;
- definir métodos auxiliares para criação de novas instâncias a partir de outras existentes.
O código, mesmo que escrito no “piloto automático” por programadores mais experientes, abre possibilidade para erros de codificação e lógica, demandando, para evitar prejuízos, a escrita de mais testes de unidade.
using System;
class Program
{
static void Main()
{
var fn1 = new FullName("Elemar", "Rodrigues Severo Júnior");
var fn2 = fn1.WithLastName("Jr.");
var fn3 = fn2.WithLastName("Rodrigues Severo Júnior");
Console.WriteLine(fn1.Equals(fn2));
Console.WriteLine(fn1.Equals(fn3));
}
}
public class FullName : IEquatable<FullName>
{
public string FirstName { get; }
public string LastName { get; }
public FullName(string firstName, string lastName) =>
(FirstName, LastName) = (firstName, lastName);
public bool Equals([AllowNull] FullName other)
{
if (other == null)
{
return false;
}
return
FirstName == other.FirstName &&
LastName == other.LastName;
}
public FullName WithFirstName(string firstName) =>
new FullName(firstName, LastName);
public FullName WithLastName(string lastName) =>
new FullName(FirstName, lastName);
}
A evolução da C# tem trazido para a linguagem a solução de problemas recorrentes tornando as implementações menos imperativas e mais declarativas.
Na C# 9, por exemplo, teremos:
- init-only properties que tornaram desnecessários construtores apenas para a inicialização dos campos;
- O modificador data que, entre outras coisas, fornecerá implementações especializadas de
Equals
eGetHashcode
; - with-expressions que permitirá criação de novas instâncias a partir de outras existentes;
- new-expressions que inferem o tipo de um objeto a partir do tipo declarado para a variável.
Tudo isso deixando o código muito mais breve.
using System;
FullName fn1 = new
{
FirstName = "Elemar",
LastName = "Rodrigues Severo Júnior"
};
var fn2 = fn1 with { LastName = "Jr."};
var fn3 = fn2 with { LastName = "Rodrigues Severo Júnior" };
Console.WriteLine(fn1.Equals(fn2));
Console.WriteLine(fn1.Equals(fn3));
public data class FullName
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Aliás, o método Main
tornou-se opcional. A partir da C# 9, há suporte direto para o que o time da Microsoft chama top level programs. Nesses cenários, o parâmetro args
continua disponível implicitamente.
Finalmente, há ainda uma novidade que permite tornar a construção de DTOs ainda mais econômica. São os positional records.
using System;
using static System.Console;
var fn1 = new FullName("Elemar", "Rodrigues Severo Júnior");
var fn2 = fn1 with { LastName = "Jr."};
var fn3 = fn2 with { LastName = "Rodrigues Severo Júnior" };
WriteLine(fn1.Equals(fn2));
WriteLine(fn1.Equals(fn3));
public data class FullName(string FirstName, string LastName);
Tudo isso representa menos código para ser escrito pelo programador. Também implica na redução na quantidade de testes de unidade necessários para evitar erros de codificação ou lógica. Logo, maior produtividade.
Por outro lado, a cada nova versão a especificação da linguagem fica maior, assim como a curva de aprendizagem. Concordamos que o último exemplo, definitivamente, não parece C#?
Esse último código me lembrou um pouco a linguagem Scala.