Tipos Complexos
Enquanto os tipos primitivos (como int, bool, double) representam valores únicos e simples, a verdadeira força de uma linguagem orientada a objetos como o C# reside na capacidade de criar Tipos Complexos. Estes são tipos de dados que nós, desenvolvedores, definimos para modelar os conceitos do mundo real, como um Cliente, um Pedido ou um Produto.
Um tipo complexo é uma estrutura de dados que agrupa múltiplos valores (sejam eles primitivos ou outros tipos complexos) e, crucialmente, pode conter comportamento associado a esses dados (através de métodos).
No C#, os tipos complexos se dividem em duas categorias fundamentais, cuja compreensão é absolutamente crítica: Tipos de Valor (Value Types) e Tipos de Referência (Reference Types).
A Divisão Fundamental: Valor vs. Referência
A diferença entre tipos de valor e de referência está em onde e como seus dados são armazenados na memória.
Tipos de Valor (
struct,enum): Uma variável de um tipo de valor contém diretamente seus dados. Quando você atribui uma variável de valor a outra, o valor é copiado. Elas geralmente vivem em uma área da memória chamada Stack (Pilha), que é muito rápida e gerenciada de forma eficiente.Tipos de Referência (
class,record,string, arrays): Uma variável de um tipo de referência não contém o objeto em si. Em vez disso, ela contém uma referência (um endereço, como um ponteiro) para o local na memória onde o objeto real está armazenado. O objeto em si vive em uma área da memória chamada Heap (Monte). Quando você atribui uma variável de referência a outra, apenas a referência é copiada, não o objeto. Ambas as variáveis passam a apontar para o mesmo objeto.
Diagrama de Memória: Stack vs. Heap
Este diagrama visualiza a diferença. myPoint (um struct) vive inteiramente na Stack. myCustomer (uma class) é apenas um ponteiro na Stack que aponta para o objeto real no Heap.
Tipos de Valor em Detalhe
struct: Criando Tipos de Valor Leves
Um struct é ideal para representar pequenos grupos de dados que têm semântica de valor, ou seja, onde a identidade do objeto não importa tanto quanto seus valores. Pense em um ponto, uma cor ou uma quantia monetária.
Analogia: Um
structé como uma nota autoadesiva (Post-it®). Se você tem uma nota com "Comprar pão" e a copia para outra nota, agora você tem duas notas independentes. Amassar ou rasgar uma não afeta a outra.
Exemplo Prático: Modelando Dinheiro
enum: Para Constantes Nomeadas
Um enum (enumeração) é um tipo de valor especial que permite definir um conjunto de constantes nomeadas, melhorando a legibilidade do código.
Analogia: Os naipes de um baralho (Copas, Ouros, Paus, Espadas) ou os dias da semana. São um conjunto finito e bem conhecido de opções.
Tipos de Referência em Detalhe
class: A Espinha Dorsal da OOP
Uma class é o bloco de construção mais comum para tipos de referência. É usada para modelar entidades complexas que têm identidade, estado (que pode mudar ao longo do tempo) e comportamento.
Analogia: Uma
classé como um link para um documento no Google Docs. Se eu te enviar o link, nós dois estamos olhando e editando o mesmo documento. Se eu mudar algo, você verá a mudança instantaneamente, porque ambos temos uma referência para o mesmo objeto.
Exemplo Prático: Modelando um Cliente
O Problema da "Obsessão Primitiva" (Primitive Obsession)
Este é um code smell (um mau cheiro no código) muito comum, onde usamos tipos primitivos para representar conceitos de domínio que são, na verdade, mais complexos.
Usar
stringpara um e-mail ou CPF.Usar
decimalpara dinheiro sem a moeda.Usar
intpara um ID de produto.
Por que isso é ruim?
Perda de Significado: Um método
Process(string, string, decimal)não diz nada.Process(EmailAddress, PostalCode, Money)é auto-documentado.Ausência de Validação: Qualquer
stringpode ser passada como um e-mail, mesmo que seja inválida. A lógica de validação fica espalhada por toda a aplicação.Comportamento Desassociado: Você não pode fazer
money.Add(otherMoney)semoneyfor apenas umdecimal. A lógica de negócio não tem um lugar para morar.
A Solução: Crie Pequenos Tipos Complexos (Value Objects)
A solução é criar pequenos structs ou classes/records que encapsulam o valor e o comportamento do conceito de domínio. Estes são frequentemente chamados de Value Objects.
Exemplo: De string para EmailAddress
Antes (Ruim):
Depois (Bom):
record: Uma Alternativa Moderna para Imutabilidade
Introduzido em C# 9, um record é um tipo de referência (ou valor, com record struct) que o compilador otimiza para imutabilidade e comparações baseadas em valor. Ele gera automaticamente construtores, propriedades, métodos de igualdade (Equals, GetHashCode) e um ToString() legível.
É a escolha perfeita para DTOs e Value Objects.
Tabela Resumo: class vs. struct
Característica |
|
|
|---|---|---|
Armazenamento | A referência fica na Stack, o objeto fica no Heap. | Geralmente armazenado inteiramente na Stack. |
Atribuição | Copia a referência. Ambas as variáveis apontam para o mesmo objeto. | Copia a instância inteira. Cria um objeto independente. |
Valor Padrão |
|
|
Herança | Suporta herança de outras classes. | Não pode herdar de outra classe (mas pode implementar interfaces). |
Uso Ideal | Entidades com identidade, estado mutável e comportamento complexo ( | Objetos pequenos e imutáveis com semântica de valor ( |