Na versão 7.0 do C# foi introduzido o conceito de Pattern Matching, que tem o intuito de evitar a necessidade da implementação de typecast. Já na versão 8.0, este conceito foi aplicado, sendo aplicado ao condicional switch, com a introdução das switch expressions.
Switch clássico
No switch clássico, utilizamos cases para comparar uma variável/objeto em relação a uma série de valores:
public Formato SelecionarFormato(object item)
{
var formato = item as FormatoPlanta?;
if (formato == null) return null;
Formato formatoSelecionado = null;
switch (formato)
{
case FormatoPlanta.Quadrado:
formatoSelecionado = new Quadrado(Largura, Altura);
break;
case FormatoPlanta.Retangulo:
formatoSelecionado = new Retangulo(Largura, Altura);
break;
case FormatoPlanta.Triangulo:
formatoSelecionado = new Triangulo(Largura, Altura, 2);
break;
}
return formatoSelecionado;
}
Implementando Pattern Matching no código
Com o pattern matching a conversão realizada no início do método acima pode ser substituída por um if
:
public Formato SelecionarFormato(object item)
{
if(item is FormatoPlanta formato)
{
Formato formatoSelecionado = null;
switch (formato)
{
case FormatoPlanta.Quadrado:
formatoSelecionado = new Quadrado(Largura, Altura);
break;
case FormatoPlanta.Retangulo:
formatoSelecionado = new Retangulo(Largura, Altura);
break;
case FormatoPlanta.Triangulo:
formatoSelecionado = new Triangulo(Largura, Altura, 2);
break;
}
return formatoSelecionado;
}
else
return null;
}
Formação Desenvolvedor C#
Conhecer a formaçãoEla também pode ser aplicada diretamente no switch:
public Formato SelecionarFormato(object item)
{
switch (item)
{
case FormatoPlanta formato when formato is FormatoPlanta.Quadrado:
return new Quadrado(Largura, Altura);
case FormatoPlanta formato when formato is FormatoPlanta.Retangulo:
return new Retangulo(Largura, Altura);
case FormatoPlanta formato when formato is FormatoPlanta.Triangulo:
return new Triangulo(Largura, Altura, 2);
default:
return null;
}
}
Agora que conhecemos as opções disponíveis atualmente, vamos conhecer a switch expression.
Utilizando a switch expression
Antes de mais nada, para fazer uso das switch expressions, a aplicação precisa ser configurada para o C# 8.0:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
</Project>
Com isso, o código pode ser refatorado novamente para a switch expression:
public Formato SelecionarFormato(object item)
{
return item switch
{
FormatoPlanta.Quadrado => new Quadrado(Largura, Altura),
FormatoPlanta.Retangulo => new Retangulo(Largura, Altura),
FormatoPlanta.Triangulo => new Triangulo(Largura, Altura, 2),
_ => null
};
}
Note que a variável/objeto a ser analisado vem antes da cláusula switch
. Foi dispensado o uso do case, é informado apenas os valores a serem testados. No lugar de dois pontos, utiliza-se o =>
. Para o valor padrão (default
), é utilizado o underline.
Assim como no switch padrão, as opções são analisadas na ordem que forem informadas, então a opção padrão sempre deve ser a última da expressão.
Da mesma forma que o operador ternário, o resultado da switch expression precisa ser atribuída a uma variável (ou ser retornada como no exemplo acima).
Com isso, esta expressão só pode ser aplicada em cláusulas switch onde há retorno de dados. Caso nada for retornado, como no exemplo abaixo:
foreach (var item in formas)
{
switch(item){
case Triangulo t:
t.Perimetro();
break;
case Retangulo r:
r.Area();
break;
case 10:
Console.WriteLine("Item é 10");
break;
case null:
Console.WriteLine("Item é nulo");
break;
case Retangulo r when r.Largura > 0:
r.Area();
break;
case var i:
Console.WriteLine("Item é do tipo {0}", i?.GetType().Name);
break;
}
}
Este recurso não pode ser aplicado.
Além disso, após as setas da switch expression (=>
) só é aceita uma expressão. Assim, caso queira processar um bloco de código, você pode contornar esta limitação com lambda:
public Formato SelecionarFormato(object item)
{
return item switch
{
FormatoPlanta.Quadrado => ((Func<Formato>)(() => {
Console.WriteLine("Quatrado");
return new Quadrado(Largura, Altura);
}))(),
FormatoPlanta.Retangulo => new Retangulo(Largura, Altura),
FormatoPlanta.Triangulo => new Triangulo(Largura, Altura, 2),
_ => null
};
}
Por fim, no exemplo apresentado aqui não ocorre as situações, mas caso a cláusula switch esteja comparando uma propriedade do objeto:
string Display(object o)
{
switch (o)
{
case Ponto p when p.X == 0 && p.Y == 0:
return "origem";
//...
}
}
Pode ser aplicado o property pattern:
string Display(object o)
{
return o switch
{
Ponto { X: 0, Y: 0 } => "origem",
Ponto { X: var x, Y: var y } => $"({x}, {y})",
{} => o.ToString(), // `o` não é nulo, mas não é do tipo Ponto
null => "Nulo"
};
}
Ou o desconstrutor:
string Display(int x, int y)
=> (x, y) switch {
(0, 0) => "origem",
//...
};
Conclusão
Como é possível notar as switch expressions reduz o código e melhora a legibilidade (isso comparado com a cláusula padrão). Então caso utilize a versão 8.0 do C# não deixe de fazer uso deste ótimo recurso nas situações onde for possível.