C# Campos

Um campo (field, em inglês) é uma variável que mora dentro da classe, e não dentro de um método. Cada objeto criado com new ganha a sua própria cópia de cada campo: a maçã tem o seu estoque, o kiwi tem o dele. Os campos são o estado do objeto, aquilo que ele "lembra" entre uma linha e outra do programa.

Você vai aprender
  • Como declarar um campo e onde ele fica na classe.
  • Os valores padrão dos campos (0, false, null) e como dar um valor inicial.
  • Por que um campo string sem valor gera o aviso CS8618, e as três formas de resolver.
  • Como os métodos da classe leem e mudam os campos do próprio objeto.
  • Campos private, o costume do _camelCase e por que campos public são raros em C# de verdade.
  • Campo × variável local, e os erros CS0103, CS0236, CS0102 e CS0122.

Todos os programas desta aula rodaram com dotnet run arquivo.cs na máquina fictícia da Ana (usuária ana, computador notebook), com o .NET 10 SDK, na pasta ~/cs.


A ideia antes do código: a ficha de cada um

Na lojinha da Ana, cada produto tem uma ficha de estoque pendurada na prateleira. A ficha da maçã diz "8 unidades"; a do kiwi diz "2". Quando a Ana vende uma maçã, ela risca a ficha da maçã, e a do kiwi continua como estava.

Os campos são as linhas dessa ficha. A classe diz quais linhas toda ficha tem ("nome", "estoque"); cada objeto tem a sua ficha, com os seus números.


Declarar um campo

class Produto
{
    public string Nome = "";
    public int Estoque = 10;
}
public
O modificador de acesso: quem pode ver o campo. public = qualquer parte do programa. Sem ele, o campo é private, e só a própria classe o vê (veja mais abaixo e public, private e internal).
string / int
O tipo do campo, como numa variável comum.
Nome / Estoque
O nome do campo. Campos public usam PascalCase (maiúscula no começo).
= "" / = 10
O valor inicial (opcional). Todo produto novo nasce com o nome vazio e 10 no estoque.
onde fica
Dentro das chaves da classe, mas fora de qualquer método. É isso que faz dele um campo, e não uma variável local.

Valores padrão

Uma variável local sem valor não pode ser usada: é o erro CS0165. Um campo é diferente: se você não der valor inicial, ele começa com o valor padrão do seu tipo.

Ficha f = new Ficha();
Console.WriteLine($"Idade: {f.Idade}");
Console.WriteLine($"Altura: {f.Altura}");
Console.WriteLine($"Saldo: {f.Saldo}");
Console.WriteLine($"Ativo: {f.Ativo}");
Console.WriteLine($"Nome: [{f.Nome}]");
Console.WriteLine(f.Nome is null);

class Ficha
{
    public int Idade;
    public double Altura;
    public decimal Saldo;
    public bool Ativo;
    public string? Nome;
}
$ dotnet run padrao.cs
/home/ana/cs/padrao.cs(15,20): warning CS0649: Campo "Ficha.Nome" nunca é atribuído e sempre terá seu valor padrão null
/home/ana/cs/padrao.cs(11,16): warning CS0649: Campo "Ficha.Idade" nunca é atribuído e sempre terá seu valor padrão 0
/home/ana/cs/padrao.cs(12,19): warning CS0649: Campo "Ficha.Altura" nunca é atribuído e sempre terá seu valor padrão 0
/home/ana/cs/padrao.cs(13,20): warning CS0649: Campo "Ficha.Saldo" nunca é atribuído e sempre terá seu valor padrão 0
/home/ana/cs/padrao.cs(14,17): warning CS0649: Campo "Ficha.Ativo" nunca é atribuído e sempre terá seu valor padrão false
Idade: 0
Altura: 0
Saldo: 0
Ativo: False
Nome: []
True

Os avisos CS0649 ("nunca é atribuído e sempre terá seu valor padrão") são o compilador estranhando: ninguém nunca põe valor nesses campos, então eles serão sempre o padrão. Aqui foi de propósito, e os avisos até confirmam cada padrão. A ordem em que os avisos saem pode variar de uma compilação para outra.

Tipo do campoValor padrão
int, long, double, decimal…0
boolfalse
char'\0', um caractere invisível (o "caractere nulo")
int?, decimal?…null
string, arrays, objetosnull

É a mesma regra dos arrays: new int[3] nasce com zeros, new string[3] nasce com null.


O aviso CS8618: um string que nasce null

Repare que o campo Nome acima foi declarado como string?. Veja o que acontece com string, sem o ? e sem valor inicial:

Produto p = new Produto();
Console.WriteLine($"[{p.Nome}]");
Console.WriteLine(p.Nome.Length);

class Produto
{
    public string Nome;
    public decimal Preco;
}
$ dotnet run cs8618.cs; echo "código: $?"
/home/ana/cs/cs8618.cs(7,19): warning CS8618: O campo não anulável 'Nome' precisa conter um valor não nulo ao sair do construtor. Considere adicionar o modificador "obrigatório" ou declarar o campo como anulável.
/home/ana/cs/cs8618.cs(7,19): warning CS0649: Campo "Produto.Nome" nunca é atribuído e sempre terá seu valor padrão null
/home/ana/cs/cs8618.cs(8,20): warning CS0649: Campo "Produto.Preco" nunca é atribuído e sempre terá seu valor padrão 0
[]
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Program.<Main>$(String[] args) in /home/ana/cs/cs8618.cs:line 3
código: 134

O string sem ? promete "sempre tem texto" (veja Referências Anuláveis). Mas o padrão de um campo string é null, então todo produto nasceria quebrando a promessa. O CS8618 diz isso: "o campo não anulável Nome precisa ter um valor não nulo quando o objeto termina de nascer" (o "ao sair do construtor"). E o compilador estava certo: a linha 3 quebrou.

A tradução tem uma curiosidade: o "modificador obrigatório" é a palavra required do C#, que o tradutor traduziu junto. Em inglês: Consider adding the 'required' modifier or declaring the field as nullable.

Três formas de resolver

FormaEscritaQuando usar
Dar um valor inicialpublic string Nome = "";Existe um valor razoável para começar. É o que este guia fez até aqui.
Aceitar o brancopublic string? Nome;Faz sentido o campo ficar vazio (um apelido). Quem usa precisa conferir.
Exigir o valor ao criar o objetoum construtor que recebe o nome, ou required (veja Inicializadores e required)Não existe produto sem nome. É a forma mais segura.

Missão: rode e leia os avisos CS8618 e CS0649. Conserte só a classe: dê ao Nome o valor inicial "" e à Turma o valor inicial "1A". A saída deve ser Bia, turma 1A, sem nenhum aviso.

Aluno a = new Aluno();
a.Nome = "Bia";
Console.WriteLine($"{a.Nome}, turma {a.Turma}");

class Aluno
{
    public string Nome;
    public string Turma;
}
Aluno a = new Aluno();
a.Nome = "Bia";
Console.WriteLine($"{a.Nome}, turma {a.Turma}");

class Aluno
{
    public string Nome = "";
    public string Turma = "1A";
}

Métodos que usam os campos

Os campos ficam realmente úteis quando os métodos da própria classe trabalham com eles. Dentro da classe, um método escreve só Estoque, sem ponto, e isso quer dizer "o estoque deste objeto":

Produto maca = new Produto();
maca.Nome = "maçã";
Produto kiwi = new Produto();
kiwi.Nome = "kiwi";
kiwi.Estoque = 3;

maca.Vender();
maca.Vender();
kiwi.Vender();

maca.Mostrar();
kiwi.Mostrar();

class Produto
{
    public string Nome = "";
    public int Estoque = 10;

    public void Vender()
    {
        Estoque = Estoque - 1;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{Nome}: {Estoque} no estoque");
    }
}
$ dotnet run estoque.cs
maçã: 8 no estoque       « começou com 10 (valor inicial) e vendeu 2
kiwi: 2 no estoque       « começou com 3 (mudado na linha 5) e vendeu 1
public int Estoque = 10;
Todo produto nasce com 10. A maçã ficou com esse valor; o kiwi trocou para 3 logo depois de nascer.
public void Vender()
Um método do produto, sem static e sem parâmetros. Ele não precisa receber o estoque: já tem acesso aos campos do objeto.
Estoque = Estoque - 1;
Diminui o estoque do objeto que recebeu o pedido. Em maca.Vender(), é o da maçã; em kiwi.Vender(), o do kiwi.
maca.Vender();
Lê-se "maçã, venda uma unidade". O objeto antes do ponto é quem executa o método.

Esses métodos se chamam métodos de instância (de objeto). Os que você escreveu em Métodos, soltos nas instruções de nível superior, não pertenciam a objeto nenhum. A palavra que deixa explícito "o campo deste objeto" é o this (this.Estoque), assunto da aula this.

Brinque com o código: crie um terceiro produto (uma uva), venda 4 unidades dela e mostre-a. Depois mude o valor inicial de Estoque na classe para 20 e veja quais produtos mudam.

Produto maca = new Produto();
maca.Nome = "maçã";
Produto kiwi = new Produto();
kiwi.Nome = "kiwi";
kiwi.Estoque = 3;

maca.Vender();
maca.Vender();
kiwi.Vender();

maca.Mostrar();
kiwi.Mostrar();

class Produto
{
    public string Nome = "";
    public int Estoque = 10;

    public void Vender()
    {
        Estoque = Estoque - 1;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{Nome}: {Estoque} no estoque");
    }
}

Campos private

Do jeito que está, qualquer linha do programa pode fazer kiwi.Estoque = -50;, e a classe não tem como impedir. Por isso, em C# de verdade, os campos quase sempre são private: só os métodos da própria classe mexem neles. O resto do programa usa os métodos.

Contador c = new Contador();
c.Somar();
c.Somar();
c.Somar();
Console.WriteLine($"Cliques: {c.Valor()}");

class Contador
{
    private int _cliques;

    public void Somar()
    {
        _cliques++;
    }

    public int Valor()
    {
        return _cliques;
    }
}
$ dotnet run priv.cs
Cliques: 3
private int _cliques;
Só o Contador enxerga este campo. O nome segue o costume do C# para campos privados: _camelCase, com sublinhado na frente. O sublinhado avisa, de relance, "isto é um campo privado, não uma variável local".
_cliques++;
O único jeito de aumentar o contador é pelo Somar(), de um em um. Ninguém de fora consegue pôr 100 de uma vez.
public int Valor()
Um método que só devolve o valor do campo, para quem está fora poder ler.

Tentar mexer no campo de fora (c._cliques = 100;) é o erro CS0122:

$ dotnet run priv.cs
/home/ana/cs/priv.cs(6,3): error CS0122: "Contador._cliques" é inacessível devido ao seu nível de proteção

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Campos public: só nos primeiros exemplos

Escrever um método só para ler (Valor()) e outro só para mudar é trabalhoso. O C# tem um recurso feito para isso, as propriedades: por fora parecem campos (c.Cliques), por dentro passam por um get e um set que você controla. É a próxima aula. Na prática, um código C# profissional usa campos private e propriedades public; os campos public destas primeiras aulas são só para começar simples.


Campo × variável local

CampoVariável local
Onde é declaradoNa classe, fora dos métodosDentro de um método (ou nas instruções de nível superior)
Quanto tempo viveEnquanto o objeto existirAté o fim do bloco { } (veja Escopo)
Quantas cópiasUma por objetoUma por execução do método
Sem valor inicialComeça com o padrão (0, false, null)Não pode ser usada: CS0165
Pode ter public/privateSimNão
NomeEstoque (public), _estoque (private)estoque
$ dotnet run local.cs        « int total; Console.WriteLine(total);
/home/ana/cs/local.cs(2,19): error CS0165: Uso de variável local não atribuída "total"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Existem também campos que pertencem à classe, e não a cada objeto (static), e campos que não podem mudar depois que o objeto nasce (readonly). Cada um tem a sua aula: static e readonly e init.


Quando dá errado

CS0103: usar o campo sem dizer de qual objeto
Nome = "maçã";

class Produto
{
    public string Nome = "";
}
$ dotnet run e103.cs
/home/ana/cs/e103.cs(1,1): error CS0103: O nome "Nome" não existe no contexto atual

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: fora da classe, Nome sozinho não significa nada: o campo só existe dentro de cada produto. "Não existe no contexto atual" = "aqui, ninguém tem esse nome".

Como resolver: crie o objeto e use o ponto: Produto p = new Produto(); p.Nome = "maçã";. O Nome sem ponto só funciona dentro dos métodos da própria classe.

CS0236: um campo calculado a partir de outro
Produto p = new Produto();
Console.WriteLine(p.Total);

class Produto
{
    public decimal Preco = 5m;
    public int Quantidade = 3;
    public decimal Total = Preco * Quantidade;
}
$ dotnet run e236.cs
/home/ana/cs/e236.cs(8,28): error CS0236: Um inicializador de campo não pode referenciar o campo, o método ou a propriedade não estática "Produto.Preco"
/home/ana/cs/e236.cs(8,36): error CS0236: Um inicializador de campo não pode referenciar o campo, o método ou a propriedade não estática "Produto.Quantidade"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: o valor inicial de um campo (o "inicializador") não pode usar outros campos do objeto. Além disso, mesmo que pudesse, o Total seria calculado uma vez só, no nascimento, e ficaria errado quando o preço mudasse.

Como resolver: transforme o total num método, que calcula na hora: public decimal Total() { return Preco * Quantidade; }. Na próxima aula, você verá que uma propriedade calculada faz isso ainda melhor.

CS0102: dois campos com o mesmo nome
$ dotnet run e102.cs
/home/ana/cs/e102.cs(7,19): error CS0102: O tipo "Produto" já contém uma definição para "Nome"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: a classe declarou public string Nome = ""; duas vezes (linhas 5 e 7). Cada nome só pode aparecer uma vez por classe.

Como resolver: apague a repetida ou dê outro nome (Apelido, NomeCompleto).

CS8618: string sem valor inicial
$ dotnet run cs8618.cs
/home/ana/cs/cs8618.cs(7,19): warning CS8618: O campo não anulável 'Nome' precisa conter um valor não nulo ao sair do construtor. Considere adicionar o modificador "obrigatório" ou declarar o campo como anulável.

O que aconteceu: o campo string Nome; começaria null, mas o tipo promete texto.

Como resolver: = "", string? ou um construtor (veja acima).

CS0122: o campo é private
$ dotnet run priv.cs
/home/ana/cs/priv.cs(6,3): error CS0122: "Contador._cliques" é inacessível devido ao seu nível de proteção

O que aconteceu: uma linha fora da classe tentou usar um campo private (ou um campo sem modificador, que é private por padrão).

Como resolver: use um método (ou uma propriedade) que a classe oferece. Só troque para public se ninguém de fora puder estragar o objeto mexendo no campo.


Referência

EscritaSignificado
public int Estoque;Campo público, começa com 0
public int Estoque = 10;Campo público com valor inicial
private int _estoque;Campo privado (só a classe vê), nome em _camelCase
int estoque; dentro da classeTambém privado: private é o padrão
public string Nome = "";Texto que nunca é null (evita o CS8618)
public string? Apelido;Texto que pode ficar null
objeto.CampoUsa o campo de um objeto, de fora da classe
Campo (sem ponto), num método da classeO campo do objeto que está executando o método

Missão final: a conta da Ana

Missão: a classe Conta guarda o saldo num campo private. O método Sacar tira o dinheiro sem conferir, e a conta termina negativa. Mude o Sacar: se o valor for maior que o _saldo, mostre Saldo insuficiente e não tire nada. A última linha deve ser Saldo: R$ 70,00.

Conta conta = new Conta();
conta.Depositar(100m);
conta.Sacar(30m);
conta.Sacar(200m);
conta.Mostrar();

class Conta
{
    private decimal _saldo;

    public void Depositar(decimal valor)
    {
        _saldo = _saldo + valor;
    }

    public void Sacar(decimal valor)
    {
        _saldo = _saldo - valor;
    }

    public void Mostrar()
    {
        Console.WriteLine($"Saldo: {_saldo:C}");
    }
}
Conta conta = new Conta();
conta.Depositar(100m);
conta.Sacar(30m);
conta.Sacar(200m);
conta.Mostrar();

class Conta
{
    private decimal _saldo;

    public void Depositar(decimal valor)
    {
        _saldo = _saldo + valor;
    }

    public void Sacar(decimal valor)
    {
        if (valor > _saldo)
        {
            Console.WriteLine("Saldo insuficiente");
            return;
        }
        _saldo = _saldo - valor;
    }

    public void Mostrar()
    {
        Console.WriteLine($"Saldo: {_saldo:C}");
    }
}

Exercícios

1. Valor padrão

Qual é o valor de um campo public bool Pago; num objeto recém-criado? (escreva como o C# mostra)

2. Campo privado

Complete a declaração de um campo privado de pontos, inteiro, no estilo do C#:

int _pontos;

3. Sem aviso

Complete para que o campo Cidade nunca seja null e não gere o CS8618:

public string Cidade

4. A classe tem public int Estoque = 10;. A Ana cria a e b e chama a.Vender() três vezes (cada uma tira 1). Quanto vale b.Estoque?

5. Qual é a diferença de uma variável local para um campo, quando nenhum dos dois recebe valor?

6. Por que os campos em C# profissional costumam ser private?


Resumo

  • Campo = variável declarada na classe, fora dos métodos. Cada objeto tem a sua cópia.
  • Sem valor inicial, o campo começa com o padrão: 0, false, null. O CS0649 avisa quando nunca recebe valor.
  • Um campo string sem valor nasce null e gera o CS8618: use = "", string? ou um construtor.
  • Dentro dos métodos da classe, Estoque (sem ponto) é o campo do objeto que recebeu o pedido.
  • Em C# de verdade, campos são private e se chamam _camelCase; o acesso de fora é por métodos e, principalmente, por propriedades.
  • Erros: CS0103 (campo sem objeto), CS0236 (inicializador usando outro campo), CS0102 (nome repetido), CS0122 (campo privado).