Design: Union Types

- Leitura
- 4 min
- Código
- 4
Olá!
Este é mais um post da seção Design e nele vamos falar sobre uma novidade muito interessante do C#↳C#68 conteúdosNovidades do C# 8.0: Default Interface MembersDev (Back & Front) · jun 2019C# 8.0 – Conheça suas novas funcionalidadesDev (Back & Front) · mai 2019Novidades do C# 8.0: Switch ExpressionsDev (Back & Front) · mar 2019Ver tudo em Dev (Back & Front) → 15 que se avizinha: union types.
Vamos lá!
Union Types? O que são?
Union types podem ser entendidos como um conjuto de tipos intercambiáveis que podem ser trafegados pela aplicação sem a necessidade de apresentar diferentes assinaturas para cada um.
Vamos a um exemplo simples:
public union ProcessPaymentResult
{
case Success(string TransactionId, decimal Amount);
case InsufficientFunds(decimal Balance, decimal AttemptedAmount);
}
Repare que temos a declaração do union type seguida da declaração dos tipos que o compõe. Esse case antes de cada tipo indica que é possível atribuir propriedades os subtipos como fazemos em records posicionais.
Fácil. Não?
Agora vamos ao exemplo de uso:
public class PaymentProcessor
{
public ProcessPaymentResult ExecutePayment(decimal amount)
{
if (amount > 1000)
return new ProcessPaymentResult.InsufficientFunds(Balance: 250.00m, AttemptedAmount: amount);
return new ProcessPaymentResult.Success(TransactionId: "TXN-98765", Amount: amount);
}
}
...
ProcessPaymentResult result = paymentProcessor.ExecutePayment(200);
return result switch
ProcessPaymentResult.Success => Ok()
ProcessPaymentResult.InsuficientFunds => UnprocessableEntity(¨Insuficient funds¨);
Perceba que temos algo espetacular aqui: é possível retornar mais de um tipo num mesmo método e validá-lo para tomar uma decisão.
Aí você poderia me perguntar: mas qual a diferença entre um union type e polimorfismo? E essa é uma ótima pergunta. Vamos respondê-la já.
Mais que polimorfismo
Muito embora a implementação de union type tenha o polimorfismo como base, ele aplica restrições que o polimorfismo não consegue dar conta facilmente.
Não sei se você reparou bem no exemplo acima, mas no pattern matching não existe o trecho _ => indicando o caso padrão. E isso é proposital, os union types tem o que chamamos de checagem exaustiva (exaustiveness check) que consiste em uma checagem do compilador pela completa apresentação dos tipos do union em um pattern matching. Ou seja, se tivéssemos mais erros em nosso tipo, além do InsuficientFunds ele seria obrigatório no switch acima, do contrário o compilador avisaria que os casos possíveis não foram verificados. Legal, não?
Uma outra diferença é a não necessidade da criação de uma classe abstrata ou interface para cada caso, o union type já implementa uma interface gratuitamente. Vamos vê-la.
namespace System.Runtime.CompilerServices
{
public interface IUnion
{
object? Value { get; }
}
}
É uma interface como tantas outras mas observe: ela pertence ao namespace System.Runtime.CompilerServices, ou seja, ela faz parte do repertório nativo do compilador do C#, não é um tipo convencional.
Ao mesmo tempo ela implementa apenas uma propriedade, Value , que é um object?.
Nota: Uma vez que Value é um object? isso significa duas coisas: 1) ele pode ser nulo e; 2) por ser um object qualquer value type informado à propriedade sofrerá, necessariamente, boxing — o que pode oferecer uma desvantagem em desempenho se o tipo for utilizado exaustivamente. Ou seja, use o recurso com parcimônia pois o uso indiscriminado pode gerar mais coletas do Garbage Collector, gerando mais tempos de pausa em sua aplicação.
Além da interface IUnion existe também um atributo novo, chamado Union que é responsável por informar ao compilador que um union type está sendo utilizado. Veja abaixo:
[Union]
public readonly struct ProcessPaymentResult : IUnion
{
private readonly object? _value;
private readonly Success _success;
private readonly InsufficientFunds _insufficientFunds;
public object? Value => _value;
}
Agora imagine o trabalho de implementar vários tipos assim ¨manualmente¨ (se bem que hoje em dia uma IA↳Inteligência artificial440 conteúdosUX e IA: Transformando Experiências Digitais com Inteligência ArtificialProduto & UX · jan 2025MCP: O que é e por que você vai ouvir falar disso em breve?AI · jul 2025IA generativa e a urgência de reconstruir nossa relação com a verdadeAI · jun 2025Ver tudo em AI → o faria, mas tudo bem…) toda vez que precisasse usar esse recurso. Então o próprio C# te ajuda com um açúcar sintático e trabalha nos bastidores por você.
Nota: Enquanto IUnion possui um object? como propriedade os tipos gerados são structs. Essa é uma forma de contornar a pressão sobre o Garbage Collector para reduzir sua influência sobre a aplicação. Sem dúvidas uma bela escolha!
E, por fim, temos uma última mas importantíssima vantagem dos union types: a não necessidade de checagem por nulidade.
Até este momento, em caso de uso do polimorfismo, éramos obrigados a nos proteger do temido NullReferenceException fazendo checagens após o retorno de um dado valor. Agora isso não é mais necessário por conta da checagem exaustiva e, mais do que isso: como a decisão sobre o que fazer com um dado resultado agora é uma regra de negócio, somos obrigados a verificar o tipo retornado antes de seguir em frente.
Ou seja: mais um passo em direção à ausência de nulidades no código.
Conclusão
Union types são uma bela adição ao C# 15 e, sem dúvidas, vai tornar o código mais versátil e mais seguro.
Vale muito a pena baixar o .NET SDK 11 e fazer experimentos com a versão release candidate enquanto não chegamos ao lançamento oficial. Você, com certeza, vai gostar da novidade e pensar em cenários onde esse recurso seria/será útil.
Gostou deste artigo? Me deixe saber por uma reação ou comentário. Fique à vontade para me procurar em minhas redes sociais para me contar o que achou.
Muito obrigado por ler até aqui e até a próxima!







José Carlos Macoratti





