Na linguagem de programação C#, um objeto pode ser um tipo de valor ou um tipo de referência. Embora um objeto seja um objeto, a diferença entre tipos de valor e tipos de referência se resume à natureza de suas variáveis. Uma variável de um tipo de valor contém um objeto ou uma instância do tipo. Uma variável de um tipo de referência contém uma referência a um objeto ou uma referência a uma instância do tipo.

C# também nos permite criar o que chamamos de objetos de valor, um tipo especial de objeto usado em design orientado a domínio que nos permite articular conceitos de domínio de forma simples, clara e concisa. Neste artigo examinaremos objetos de valor, discutiremos seus benefícios e ilustraremos como podemos usá-los em C#.

Crie um projeto de aplicativo de console no Visual Studio

Primeiro, vamos criar um projeto de aplicativo de console .NET Core no Visual Studio. Supondo que o Visual Studio 2022 esteja instalado em seu sistema, siga as etapas descritas abaixo para criar um novo projeto de aplicativo de console principal .NET.

  1. Inicie o IDE do Visual Studio.
  2. Clique em “Criar novo projeto”.
  3. Na janela “Criar novo projeto”, selecione “Aplicativo de console (.NET Core)” na lista de modelos exibida.
  4. Clique em Avançar.
  5. Na janela “Configure seu novo projeto”, especifique o nome e o local do novo projeto.
  6. Clique em Avançar.
  7. Na janela “Informações adicionais”, escolha “.NET 8.0 (Long Term Support)” como a versão do framework que deseja usar.
  8. Deixe as caixas de seleção “Não usar instruções de nível superior” e “Ativar publicação AOT nativa” desmarcadas.
  9. Clique em Criar.

Usaremos esse projeto de aplicativo de console .NET 8 para trabalhar com objetos de valor nas seções subsequentes deste artigo.

O que são objetos de valor em C#?

Na linguagem C#, um objeto de valor é definido como um objeto que pode ser identificado apenas pelo estado de suas propriedades. Objetos de valor representam um aspecto descritivo de um domínio sem possuir identidade. Por outras palavras, os objectos de valor representam elementos do design com os quais nos preocupamos apenas em termos dos seus conteúdos e não das suas identidades.

No design orientado a domínio (DDD), o padrão de objeto de valor representa objetos sem identidade e oferece suporte à igualdade com base em seus atributos. Deve-se notar aqui que, ao contrário dos objetos de valor, os objetos de entidade possuem identidades distintas. Ao contrário dos objetos de entidade, que são definidos por sua identidade e possuem estado mutável, os objetos de valor são definidos por seus atributos e são imutáveis.

Imutabilidade significa que você não pode alterar um objeto de valor depois de criado; quaisquer operações executadas em objetos de valor criarão uma nova instância. A imutabilidade dos objetos de valor traz importantes benefícios de desempenho. Como dois objetos de valor são considerados iguais se contiverem valores idênticos, mesmo que sejam objetos diferentes, eles se tornam intercambiáveis. Em outras palavras, a imutabilidade permite a reutilização de objetos.

Um exemplo de objetos de valor em C#

Considere a seguinte classe chamada Address, que contém algumas propriedades e um construtor de argumento.

public class Address
{
    public string HouseNo { get; set; }
    public string Street { get; set; }
    public string ZipCode { get; set; }
    public string City { get; set; }
    public Address(string houseno, string street,
    string zipCode, string city)
    {
        HouseNo = houseno;
        Street = street;
        ZipCode = zipCode;
        City = city;
    }
}

Você pode criar uma instância desta classe e atribuir valores às suas propriedades usando o código a seguir.

Address address = new Address("505", "Woodland Street", "CR2 8EN", "London");

No entanto, como a classe Address aqui expõe setters públicos, você pode facilmente alterar os valores de suas propriedades fora da classe. Isto viola uma das características básicas dos objetos de valor, nomeadamente o seu suporte à imutabilidade.

Podemos corrigir isso redesenhando a classe Address conforme mostrado abaixo.

public class Address
{
    public string HouseNo { get; private set; }
    public string Street { get; private set; }
    public string ZipCode { get; private set; }
    public string City { get; private set; }
    public Address(string houseno, string street,
    string zipCode, string city)
    {
        HouseNo = houseno;
        Street = street;
        ZipCode = zipCode;
        City = city;
    }
}

Como você pode ver acima, as propriedades da nossa nova classe Address contêm setters privados, que não permitem alterações no valor de nenhuma dessas propriedades fora da classe, preservando assim a imutabilidade. Se você executar este programa, será saudado com o erro mostrado na Figura 1 abaixo.

Figura 1. Objetos de valor são imutáveis.

Você também pode implementar objetos de valor usando registros em C#. Para fazer isso, você usa a palavra-chave record para definir um tipo de registro que encapsula dados da mesma forma que fizemos com a classe Author anteriormente. O trecho de código a seguir mostra como você pode definir um tipo de registro em C#.

public record Address(string Houseno, string Street, string ZipCode, string City);

Melhores práticas para usar objetos de valor em C#

Ao trabalhar com objetos de valor em C#, você deve seguir estas práticas recomendadas:

  • Implemente verificações de igualdade adequadas com base no valor para evitar inconsistências e erros.
  • Use objetos de valor para representar conceitos no modelo de domínio somente quando achar apropriado.
  • Use objetos de valor criteriosamente porque eles apresentam sobrecargas de desempenho.
  • Considere implementar a lógica de validação no construtor do objeto de valor para impor regras e restrições de negócios quando o objeto de valor for criado.
  • Siga os padrões e convenções de nomenclatura recomendados para garantir clareza, consistência e legibilidade em suas implementações de objetos de valor.

Objetos de valor encapsulam tipos primitivos e possuem duas propriedades principais, ou seja, não possuem identidade e são imutáveis. Os objetos de valor podem simplificar estruturas de dados complexas encapsulando dados relacionados em uma única unidade. Isso garante a integridade dos dados, impõe digitação forte, reduz erros e melhora a legibilidade do código-fonte. Você pode aproveitar os objetos de valor para aprimorar a clareza e a capacidade de manutenção do seu código C#, fornecendo uma representação mais expressiva dos conceitos de domínio.

Normalmente, a criação de objetos de valor em C# envolve substituir a comparação de igualdade e implementar a semântica apropriada baseada em valor. Uma prática típica envolve substituir os métodos Equals() e GetHashCode() e fornecer outros operadores para comparação. Normalmente, usamos uma classe base que compreende esses métodos que todas as implementações de objetos de valor devem estender.

No exemplo acima, forneci uma implementação básica de objetos de valor em C#. Em uma postagem futura aqui, explorarei como podemos implementar uma classe base de objeto de valor e discutirei conceitos mais avançados.