Switch Expressions no C# 8.0

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;
}
Desenvolvedor C# Pleno
Formação: Desenvolvedor C# Pleno
A formação Desenvolvedor C# nível Pleno da TreinaWeb tem um enfoque sobre a conectividade entre o .NET Framework e os bancos de dados relacionais através do ADO.NET. Também serão abordados os recursos para desenvolvedores que o Oracle e o MySQL oferecem, como functions, stored procedures e triggers.
CONHEÇA A FORMAÇÃO

Ela 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.

Deixe seu comentário
Share

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.