O C# 7.0 introduziu uma série de melhorias focadas em simplificar o consumo de dados, reduzir a verbosidade do código e otimizar o desempenho. Estas novidades, integradas ao Visual Studio 2017, trazem construções que tornam a lógica de negócios mais expressiva e eficiente.
Variáveis Out
Enteriormente, ao utilizar parâmetros out, era necessário declarar a variável antes de passá-la para o método, especificando seu tipo explicitamente. Com o C# 7.0, é possível declarar a variável diretamente no ponto de chamada.
public void ExibirDimensoes(Retangulo r)
{
r.CalcularDimensoes(out int largura, out int altura);
Console.WriteLine($"Largura: {largura}, Altura: {altura}");
}
A variável declarada fica disponível no escopo do bloco envolvente. Além disso, o compilador pode inferir o tipo, permitindo o uso de var:
r.CalcularDimensoes(out var largura, out var altura);
Isso é particularmente útil em padrões Try, onde o retorno booleano indica sucesso e o parâmetro out contém o resultado:
public void ProcessarIdade(string entrada)
{
if (int.TryParse(entrada, out var idade))
{
Console.WriteLine($"Idade válida: {idade}");
}
else
{
Console.WriteLine("Entrada inválida.");
}
}
Correspondência de Padrões (Pattern Matching)
A correspondência de padrões permite testar se um valor possui uma determinada "forma" e extrair informações dele quando há correspondência. O C# 7.0 aprimora as expressões is e as instruções switch.
Expressões Is com Padrões
Agora é possível usar padrões de tipo e constantes diretamente na expressão is:
public void AnalisarDados(object dado)
{
if (dado is null) return;
if (!(dado is decimal valor)) return;
Console.WriteLine($"Valor processado: {valor}");
}
Instruções Switch Aprimoradas
O switch foi generalizado para aceitar qualquer tipo e utilizar padrões nas cláusulas case, além de permitir condições adicionais com a palavra-chave when.
switch(veiculo)
{
case Carro c when c.Portas == 4:
Console.WriteLine($"Sedan com {c.Portas} portas.");
break;
case Carro c:
Console.WriteLine($"Carro comum com {c.Portas} portas.");
break;
case Moto m:
Console.WriteLine($"Motocicleta cilindrada: {m.Cilindradas}cc");
break;
case null:
throw new ArgumentNullException(nameof(veiculo));
default:
Console.WriteLine("Tipo de veículo desconhecido.");
break;
}
A ordem das cláusulas case agora é estritamente avaliada de cima para baixo, semelhante aos blocos catch. O compilador alertará sobre casos inalcançáveis.
Tuplas
Retornar múltiplos valores de um método costumava ser verboso. O C# 7.0 introduz tipos e literais de tupla, eliminando a necessidade de criar classes temporárias ou usar out em excesso.
(string nome, int idade, bool ativo) ObterPerfilUsuario(string email)
{
// Lógica de busca no banco de dados
return ("João Silva", 30, true);
}
O chamador pode acessar os elementos pelos nomes personalizados:
var perfil = ObterPerfilUsuario("joao@email.com");
Console.WriteLine($"Nome: {perfil.nome}, Ativo: {perfil.ativo}");
Tuplas são tipos de valor (ValueTuple), o que evita alocações desnecessárias no heap, melhorando o desempenho em cenários de alta frequência.
Desconstrução
A desconstrução permite dividir uma tupla (ou qualquer objeto que implemente o método Deconstruct) em variáveis individuais.
var (nome, idade, ativo) = ObterPerfilUsuario("maria@email.com");
Console.WriteLine($"Usuário: {nome}");
Para classes personalizadas, basta implementar o método Deconstruct com parâmetros out:
public class Coordenada
{
public double X { get; }
public double Y { get; }
public Coordenada(double x, double y) { X = x; Y = y; }
public void Deconstruct(out double x, out double y)
{
x = X;
y = Y;
}
}
Isso permite desconstruir objetos diretamente:
var (posX, posY) = new Coordenada(10.5, 20.5);
Funções Locais
Funções locais permitem declarar métodos auxiliares dentro do escopo de outro método. Isso é ideal para encapsular lógica que não deve ser exposta ao restante da classe e para validar argumentos antes de executar iteradores.
public IEnumerable<int> GerarSequenciaPares(int maximo)
{
if (maximo < 0) throw new ArgumentException("O valor deve ser positivo.");
return Iterar();
IEnumerable<int> Iterar()
{
for (int i = 0; i <= maximo; i++)
{
if (i % 2 == 0) yield return i;
}
}
}</int></int>
Neste exemplo, a validação do argumento ocorre imediatamente, enquanto a execução do iterador é adiada até que a coleção seja enumerada, mantendo o código limpo e seguro.