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.
- 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
stringsem valor gera o avisoCS8618, 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_camelCasee por que campospublicsão raros em C# de verdade. - Campo × variável local, e os erros
CS0103,CS0236,CS0102eCS0122.
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
publicusam 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 campo | Valor padrão |
|---|---|
int, long, double, decimal… | 0 |
bool | false |
char | '\0', um caractere invisível (o "caractere nulo") |
int?, decimal?… | null |
string, arrays, objetos | null |
É 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
| Forma | Escrita | Quando usar |
|---|---|---|
| Dar um valor inicial | public string Nome = ""; | Existe um valor razoável para começar. É o que este guia fez até aqui. |
| Aceitar o branco | public string? Nome; | Faz sentido o campo ficar vazio (um apelido). Quem usa precisa conferir. |
| Exigir o valor ao criar o objeto | um 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
statice 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çã; emkiwi.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
Contadorenxerga 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.
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
| Campo | Variável local | |
|---|---|---|
| Onde é declarado | Na classe, fora dos métodos | Dentro de um método (ou nas instruções de nível superior) |
| Quanto tempo vive | Enquanto o objeto existir | Até o fim do bloco { } (veja Escopo) |
| Quantas cópias | Uma por objeto | Uma por execução do método |
| Sem valor inicial | Começa com o padrão (0, false, null) | Não pode ser usada: CS0165 |
Pode ter public/private | Sim | Não |
| Nome | Estoque (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
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.
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.
$ 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).
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).
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
| Escrita | Significado |
|---|---|
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 classe | També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.Campo | Usa o campo de um objeto, de fora da classe |
Campo (sem ponto), num método da classe | O 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#:
3. Sem aviso
Complete para que o campo Cidade nunca seja null e não gere o CS8618:
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. OCS0649avisa quando nunca recebe valor. - Um campo
stringsem valor nascenulle gera oCS8618: 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
privatee 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).