Propriedades (Guia de Programação em C#)

Uma propriedade é um membro que oferece um mecanismo flexível para ler, gravar ou calcular o valor de um campo de dados. As propriedades aparecem como membros de dados públicos, mas são implementadas como métodos especiais chamados acessadores. Esse recurso permite que os chamadores acessem dados facilmente e ainda ajuda a promover a segurança e a flexibilidade dos dados. A sintaxe para propriedades é uma extensão natural para os campos. Um campo define um local de armazenamento:

public class Person
{
    public string? FirstName;

    // Omitted for brevity.
}

Propriedades implementadas automaticamente

Uma definição de propriedade contém declarações para um acessador get e set que recupera e atribui o valor dessa propriedade:

public class Person
{
    public string? FirstName { get; set; }

    // Omitted for brevity.
}

O exemplo anterior mostra uma propriedade implementada automaticamente. O compilador gera um campo de suporte oculto para a propriedade. O compilador também implementa o corpo dos acessadores get e set. Todos os atributos são aplicados à propriedade implementada automaticamente. Você pode aplicar o atributo ao campo de suporte gerado pelo compilador especificando a tag field: no atributo.

Você pode inicializar uma propriedade para um valor diferente do padrão definindo um valor após a chave de fechamento da propriedade. Talvez você prefira que o valor inicial para a propriedade FirstName seja a cadeia de caracteres vazia em vez de null. Você especificaria isso conforme mostrado no código a seguir:

public class Person
{
    public string FirstName { get; set; } = string.Empty;

    // Omitted for brevity.
}

Controle de acesso

Os exemplos anteriores mostraram propriedades de leitura/gravação. Você também pode criar propriedades somente leitura ou dar acessibilidade diferente aos acessadores get e set. Suponha que sua classe Person só deva habilitar a alteração do valor da propriedade FirstName em outros métodos naquela classe. Você pode dar acessibilidade private ao acessador set, em vez de public:

public class Person
{
    public string? FirstName { get; private set; }

    // Omitted for brevity.
}

A propriedade FirstName pode ser lida em qualquer código, mas só pode ser atribuída do código na classe Person.

Você pode adicionar qualquer modificador de acesso restritivo aos acessadores get ou set. Um modificador de acesso em um acessador individual deve ser mais restritivo do que o acesso da propriedade. O código anterior é legal porque a propriedade FirstName é public, mas o acessador set é private. Você não poderia declarar uma propriedade private com um acessador public. As declarações de propriedade também podem ser declaradas protected, internal, protected internal ou até mesmo private.

Há dois modificadores de acesso especiais para acessadores set:

  • Um acessador set pode ter init como seu modificador de acesso. Esse acessador set pode ser chamado somente de um inicializador de objeto ou dos construtores do tipo. É mais restritivo do que private no acessador set.
  • Uma propriedade implementada automaticamente pode declarar um get acessador sem um set acessador. Nesse caso, o compilador permite que o acessador set seja chamado somente dos construtores do tipo. É mais restritivo do que o acessador init no acessador set.

Modifique a classe Person da seguinte maneira:

public class Person
{
    public Person(string firstName) => FirstName = firstName;

    public string FirstName { get; }

    // Omitted for brevity.
}

O exemplo anterior requer que os chamadores usem o construtor que inclui o parâmetro FirstName. Os chamadores não podem usar inicializadores de objeto para atribuir um valor à propriedade. Para dar suporte a inicializadores, você pode transformar o set em um init, conforme mostrado no seguinte código:

public class Person
{
    public Person() { }
    public Person(string firstName) => FirstName = firstName;

    public string? FirstName { get; init; }

    // Omitted for brevity.
}

Esses modificadores são frequentemente usados com o modificador required para forçar a inicialização adequada.

Propriedades obrigatórias

O exemplo anterior permite que um chamador crie um Person usando o construtor padrão, sem definir a propriedade FirstName. A propriedade alterou o tipo para uma cadeia de caracteres que permite valor nulo. A partir do C# 11, você pode exigir que os chamadores definam uma propriedade:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName) => FirstName = firstName;

    public required string FirstName { get; init; }

    // Omitted for brevity.
}

O código anterior faz duas alterações na classe Person. Primeiro, a declaração de propriedade FirstName inclui o modificador required. Isso significa que qualquer código que crie um novo Person deve definir essa propriedade usando um inicializador de objeto. Em segundo lugar, o construtor que usa um parâmetro firstName tem o atributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute. Esse atributo informa ao compilador que esse construtor define todos required os membros. Os chamadores que usam esse construtor não precisam definir propriedades required com um inicializador de objeto.

Importante

Não confunda required com não anulável. É válido definir uma propriedade required como null ou default. Se o tipo for não anulável, como string nesses exemplos, o compilador emitirá um aviso.

var aPerson = new Person("John");
aPerson = new Person{ FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();

Definições de corpo de expressão

Os acessadores de propriedade geralmente consistem em instruções de linha única. Os acessadores atribuem ou retornam o resultado de uma expressão. Você pode implementar essas propriedades como membros aptos para expressão. As definições de corpo da expressão consistem no token => seguido pela expressão à qual atribuir ou recuperar da propriedade.

Propriedades somente leitura podem implementar o acessador get como um membro apto para expressão. O exemplo a seguir implementa a propriedade Name somente leitura como um membro apto para expressão:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    public string Name => $"{FirstName} {LastName}";

    // Omitted for brevity.
}

A propriedade Name é uma propriedade computada. Não há campo de apoio para Name. A propriedade calcula isso toda vez.

Propriedades com campos de suporte

Combine o conceito de uma propriedade computada com um campo privado e crie uma propriedade avaliada armazenada em cache. Por exemplo, atualize a propriedade FullName para que a formatação da string ocorra no primeiro acesso:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Essa implementação funciona porque as propriedades FirstName e LastName são somente leitura. As pessoas podem mudar o nome. A atualização das propriedades FirstName e LastName para permitir acessadores set exige que você invalide qualquer valor armazenado em cache para fullName. Modifique os acessadores set das propriedades FirstName e LastName para que o campo fullName seja calculado novamente:

public class Person
{
    private string? _firstName;
    public string? FirstName
    {
        get => _firstName;
        set
        {
            _firstName = value;
            _fullName = null;
        }
    }

    private string? _lastName;
    public string? LastName
    {
        get => _lastName;
        set
        {
            _lastName = value;
            _fullName = null;
        }
    }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Esta versão final avalia a propriedade FullName apenas quando necessário. Se a versão calculada anteriormente é válida, ela é usada. Caso contrário, o cálculo atualizará o valor armazenado em cache. Os desenvolvedores que usam essa classe não precisam saber dos detalhes da implementação. Nenhuma dessas alterações internas afetam o uso do objeto Person.

A partir do C# 13, você pode criar partial propriedades em classes partial. A declaração de implementação de uma partial propriedade não pode ser uma propriedade implementada automaticamente. Uma propriedade implementada automaticamente usa a mesma sintaxe que uma declaração de propriedade parcial de declaração.

Propriedades

As propriedades são uma forma de campos inteligentes em uma classe ou objeto. De fora do objeto, elas parecem como campos no objeto. No entanto, as propriedades podem ser implementadas usando a paleta completa de funcionalidades do C#. Você pode fornecer validação, acessibilidade diferente, avaliação lenta ou quaisquer requisitos necessários aos seus cenários.

  • Propriedades simples que não exigem nenhum código de acesso personalizado podem ser implementadas como definições de corpo de expressão ou como propriedades implementadas automaticamente.
  • As propriedades permitem que uma classe exponha uma forma pública de obter e definir valores, enquanto oculta o código de implementação ou de verificação.
  • Um acessador de propriedade get é usado para retornar o valor da propriedade e um acessador de propriedade set é usado para atribuir um novo valor. Um acessador de propriedade init é usado para atribuir um novo valor somente durante a construção de objeto. Esses acessadores podem ter diferentes níveis de acesso. Para obter mais informações, consulte Restringindo a acessibilidade aos acessadores.
  • A palavra-chave value é usada para definir o valor que o acessor set ou init está atribuindo.
  • As propriedades podem ser de leitura/gravação (elas têm um acessador get e set), somente leitura (elas têm um acessador get, mas nenhum set) ou somente gravação (elas têm um acessador set, mas nenhum get). As propriedades somente gravação são raras.

Especificação da Linguagem C#

Para obter mais informações, veja Propriedades na Especificação da Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Confira também