C# public, private e internal
Até aqui, você escreveu public na frente de quase tudo, sem pensar muito. Essa palavra é um modificador de acesso: ela diz quem pode usar um campo, um método ou uma classe. Nesta aula você conhece todos os modificadores do C#, descobre o que vale quando você não escreve nenhum (o "padrão") e aprende a ler o erro mais comum do assunto, o CS0122.
- O que é um modificador de acesso e por que ele existe.
private("privado"): só a própria classe usa.public("público"): qualquer código usa.internal("interno"): só o mesmo projeto usa.protected("protegido"): a classe e as classes filhas.- O padrão de cada lugar: membro de classe sem modificador é
private; classe sem modificador éinternal. - Os erros reais
CS0122,CS1527eCS0050, e como consertar cada um.
Esta aula usa o que você viu em Classes e Objetos, Campos e Métodos.
A ideia antes do código: as portas da escola
Pense na escola da Ana. Nem toda porta abre para todo mundo:
- O portão da rua abre para qualquer pessoa: pais, visitantes, entregadores. Isso é
public. - O corredor das salas é só para quem é da escola: alunos e professores. Quem vem de outra escola não entra. Isso é
internal(o "da escola" é o seu projeto). - O armário da família do zelador abre para ele e para os filhos dele. Isso é
protected(os "filhos" são as classes que herdam). - A gaveta da mesa da diretora só ela abre. Isso é
private.
Num programa é igual. Uma classe guarda coisas que só ela deveria mexer (o dinheiro de um cofre, a senha de uma porta) e oferece algumas portas abertas para o resto do programa. O modificador de acesso é a placa em cada porta. E quem confere as placas é o compilador: se alguém tenta entrar por uma porta fechada, o programa nem compila.
private: só a própria classe
A Ana tem um cofrinho. O dinheiro fica guardado lá dentro, e a única forma de mexer nele é pelo método Guardar:
var cofre = new Cofre();
cofre.Guardar(50m);
cofre.Guardar(20m);
Console.WriteLine($"No cofre: {cofre.Total()}");
class Cofre
{
private decimal _dinheiro;
public void Guardar(decimal valor)
{
_dinheiro += valor;
}
public decimal Total()
{
return _dinheiro;
}
}
$ dotnet run cofre.cs No cofre: 70
- private decimal _dinheiro;
- O campo é
private: só o código dentro das chaves declass Cofrepode ler ou mudar_dinheiro. O_no começo do nome é a convenção da Microsoft para campos privados: quem lê o código já sabe que aquilo é "coisa de dentro". - public void Guardar(decimal valor)
- O método é
public: qualquer código pode chamá-lo. Ele está dentro da classe, então pode mexer em_dinheiro. - public decimal Total()
- Outra porta aberta, só para ler: devolve o valor guardado.
- cofre.Guardar(50m);
- As instruções de cima ficam fora da classe. Elas usam só as portas
public.
Tentando abrir a gaveta
Agora alguém tenta escrever direto no campo privado, por fora:
var cofre = new Cofre();
cofre.Guardar(50m);
cofre._dinheiro = 1000000m;
Console.WriteLine($"No cofre: {cofre.Total()}");
class Cofre
{
private decimal _dinheiro;
public void Guardar(decimal valor)
{
_dinheiro += valor;
}
public decimal Total()
{
return _dinheiro;
}
}
$ dotnet run cofre-erro.cs /home/ana/cs/cofre-erro.cs(3,7): error CS0122: "Cofre._dinheiro" é inacessível devido ao seu nível de proteção Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Leia a mensagem por partes:
cofre-erro.cs(3,7): linha 3, coluna 7, exatamente onde aparece o_dinheiro.CS0122: o código do erro. Em inglês, a mensagem é'Cofre._dinheiro' is inaccessible due to its protection level.- "inacessível devido ao seu nível de proteção": o campo existe, mas a placa da porta diz
private, e você está do lado de fora.
Repare que o erro aparece antes de o programa rodar. Ninguém consegue colocar um milhão no cofre da Ana "por engano": o compilador barra. Para consertar, use a porta aberta: cofre.Guardar(…).
Métodos private: ajudantes internos
Métodos também podem ser private. Isso é útil para ajudantes: pedaços de código que a classe usa por dentro, mas que não fazem sentido para quem está de fora. O caixa da lojinha da Ana registra cada venda com um método ajudante:
var caixa = new Caixa();
caixa.Vender(12.50m);
caixa.Vender(-3m);
caixa.Vender(7.25m);
Console.WriteLine($"Total do dia: {caixa.Total():C}");
class Caixa
{
private decimal _total;
public void Vender(decimal valor)
{
if (valor <= 0)
{
Registrar($"Venda recusada: {valor:C}");
return;
}
_total += valor;
Registrar($"Venda de {valor:C}");
}
public decimal Total()
{
return _total;
}
private void Registrar(string texto)
{
Console.WriteLine($"[caixa] {texto}");
}
}
$ dotnet run caixa.cs [caixa] Venda de R$ 12,50 [caixa] Venda recusada: -R$ 3,00 [caixa] Venda de R$ 7,25 Total do dia: R$ 19,75
Vender chama Registrar sem problema, porque os dois estão dentro da mesma classe. Mas, de fora, caixa.Registrar("tentando de fora"); dá o mesmo erro de antes:
$ dotnet run ajudante.cs /home/ana/cs/ajudante.cs(3,7): error CS0122: "Caixa.Registrar(string)" é inacessível devido ao seu nível de proteção Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
A vantagem: amanhã a Ana pode trocar o Registrar para gravar num arquivo em vez de mostrar na tela, ou até apagá-lo. Como ninguém de fora usa esse método, nada quebra fora da classe.
O padrão: quando você não escreve nada
Você pode declarar um campo ou um método sem modificador nenhum. Ele não fica "sem proteção": o C# aplica o modificador padrão, e o padrão de quem está dentro de uma classe é o mais fechado de todos, private.
var cofre = new Cofre();
cofre.Guardar(50m);
Console.WriteLine(cofre._dinheiro);
class Cofre
{
decimal _dinheiro;
public void Guardar(decimal valor)
{
_dinheiro += valor;
}
}
$ dotnet run padrao.cs /home/ana/cs/padrao.cs(3,25): error CS0122: "Cofre._dinheiro" é inacessível devido ao seu nível de proteção Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
decimal _dinheiro; sem modificador é o mesmo que private decimal _dinheiro;. Com métodos acontece igual, e esse é um susto comum de quem está começando: esquecer o public num método que deveria ser chamado de fora.
| Onde está a declaração | Sem modificador, vale… | Exemplo |
|---|---|---|
| Campo, método, propriedade ou construtor de uma classe (ou struct) | private | decimal _dinheiro; |
| Classe dentro de outra classe (classe aninhada) | private | class Cofre { class Chave { } } |
| Classe, struct, record, enum ou interface "solta" no arquivo (ou num namespace) | internal | class Cofre { } |
| Membro de uma interface | public | interface IVendavel { decimal Preco(); } |
| Valor de um enum | public (não aceita outro) | enum Cor { Azul, Verde } |
private escrito, ninguém tem dúvida. É o que a própria Microsoft recomenda nos seus guias de estilo.Missão: o programa não compila, porque o método Abrir está sem modificador (e, por isso, é private). Clique em Rodar e leia o erro. Depois, deixe Abrir público, sem abrir a senha, e rode de novo.
var porta = new Porta();
porta.Abrir("1234");
porta.Abrir("0000");
class Porta
{
private string _senha = "1234";
void Abrir(string tentativa)
{
if (tentativa == _senha)
{
Console.WriteLine("Porta aberta!");
}
else
{
Console.WriteLine("Senha errada.");
}
}
}
var porta = new Porta();
porta.Abrir("1234");
porta.Abrir("0000");
class Porta
{
private string _senha = "1234";
public void Abrir(string tentativa)
{
if (tentativa == _senha)
{
Console.WriteLine("Porta aberta!");
}
else
{
Console.WriteLine("Senha errada.");
}
}
}
public: qualquer código
public é o portão da rua: qualquer código, de qualquer classe, de qualquer projeto que use o seu, pode chamar. Use para as coisas que a sua classe oferece ao mundo: Guardar, Total, Vender, Abrir.
Cada coisa public é uma promessa. Se outras partes do programa (ou outras pessoas) passaram a usar Vender, mudar o nome ou os parâmetros dele quebra todo esse código. Por isso a regra dos programadores experientes é: comece fechado e só abra o que precisar.
internal: só o mesmo projeto
Um programa maior é dividido em vários projetos (você vai ver em Soluções e Vários Projetos e Bibliotecas de Classes). Cada projeto vira um arquivo compilado, chamado assembly (o .dll). internal quer dizer: visível dentro do mesmo assembly, invisível para os outros.
A Ana separou a lojinha em dois projetos: Estoque, uma biblioteca com as classes dos produtos, e Caixa, o programa que ela roda. No Estoque, o Produto é public e o Fornecedor é internal:
namespace Estoque;
public class Produto
{
public string Nome = "Caderno";
public decimal Preco = 12.50m;
}
internal class Fornecedor
{
public string Nome = "Papelaria Central";
}
using Estoque;
var p = new Produto();
Console.WriteLine($"{p.Nome}: {p.Preco:C}");
var f = new Fornecedor();
$ cd ~/projetos/Caixa $ dotnet add reference ../Estoque « o Caixa passa a usar o Estoque A referência ‘..\Estoque\Estoque.csproj’ foi adicionada ao projeto. $ dotnet run /home/ana/projetos/Caixa/Program.cs(5,13): error CS0122: "Fornecedor" é inacessível devido ao seu nível de proteção [/home/ana/projetos/Caixa/Caixa.csproj] Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O Produto passou, o Fornecedor não. É o mesmo CS0122, agora entre projetos. Apagando a última linha:
$ dotnet run Caderno: R$ 12,50
dotnet run cofre.cs, tudo fica no mesmo assembly. Então, ali, internal e public se comportam igual. É por isso que class Cofre, sem modificador (portanto internal), funciona em todos os exemplos desta aula.protected: a classe e as filhas
Uma classe pode herdar de outra: class Cachorro : Animal quer dizer "um cachorro é um animal e ganha tudo o que o animal tem". Você vai estudar isso com calma em Herança. Por agora, basta a ideia: protected é o armário da família, aberto para a classe e para as classes filhas dela, e fechado para o resto.
var rex = new Cachorro();
rex.Brincar();
rex.Brincar();
Console.WriteLine(rex._energia);
class Animal
{
protected int _energia = 10;
}
class Cachorro : Animal
{
public void Brincar()
{
_energia -= 3;
Console.WriteLine($"Rex brincou. Energia: {_energia}");
}
}
$ dotnet run protegido.cs /home/ana/cs/protegido.cs(4,23): error CS0122: "Animal._energia" é inacessível devido ao seu nível de proteção Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Dentro de Cachorro, o método Brincar usa _energia à vontade, porque Cachorro é filho de Animal. A linha 4, que está fora das duas classes, é barrada. Sem ela:
$ dotnet run protegido-ok.cs Rex brincou. Energia: 7 Rex brincou. Energia: 4
As combinações: protected internal e private protected
O C# ainda tem duas combinações, bem menos usadas. Você raramente vai escrevê-las, mas vai encontrá-las em código pronto:
protected internal: aberto para quem é do mesmo projeto ou para as classes filhas (mesmo em outro projeto). É o "ou" das duas.private protectedC# 7.2: aberto só para as classes filhas que estão no mesmo projeto. É o "e" das duas.
E existe o file C# 11, que vale só para tipos: file class Ajudante só pode ser usada no próprio arquivo em que foi escrita. Num projeto, outro arquivo que tente usá-la recebe error CS0103: O nome "Ajudante" não existe no contexto atual.
A tabela completa
Do mais aberto para o mais fechado. "Sim" quer dizer que aquele código consegue usar o membro:
| Modificador | A própria classe | Classe filha, mesmo projeto | Outra classe, mesmo projeto | Classe filha, outro projeto | Outra classe, outro projeto |
|---|---|---|---|---|---|
public | Sim | Sim | Sim | Sim | Sim |
protected internal | Sim | Sim | Sim | Sim | Não |
internal | Sim | Sim | Sim | Não | Não |
protected | Sim | Sim | Não | Sim | Não |
private protected | Sim | Sim | Não | Não | Não |
private | Sim | Não | Não | Não | Não |
Modificadores em classes
Uma classe "solta" no arquivo (ou dentro de um namespace) só pode ser public ou internal. Não faz sentido uma classe private lá: ela seria privada de quem? Não há nenhuma classe em volta para ser a "dona".
Console.WriteLine("oi");
private class Segredo
{
}
$ dotnet run topo-private.cs /home/ana/cs/topo-private.cs(3,15): error CS1527: Os elementos definidos em um namespace não podem ser declarados explicitamente como privados, protegidos, protegidos internamente ou protegidos de forma privada Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O conserto é tirar o private (a classe fica internal) ou trocá-lo por public. Uma classe dentro de outra pode, sim, ser private: aí a dona é a classe de fora.
Acessibilidade inconsistente
Uma porta aberta não pode levar a uma sala fechada. Se um método é public, os tipos que ele recebe e devolve também têm de ser visíveis para quem chama:
var loja = new Loja();
Console.WriteLine(loja.Achar().Nome);
public class Loja
{
public Produto Achar()
{
return new Produto();
}
}
class Produto
{
public string Nome = "Caderno";
}
$ dotnet run inconsistente.cs /home/ana/cs/inconsistente.cs(6,20): error CS0050: Acessibilidade inconsistente: tipo de retorno "Produto" é menos acessível do que o método "Loja.Achar()" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Loja é public, então Achar é visível até para outros projetos. Mas ele devolve um Produto, que é internal (não tem modificador). Outro projeto receberia um objeto de um tipo que ele nem pode enxergar. Há dois consertos: deixar Produto public ou fechar a Loja (tirar o public dela). Com um parâmetro do tipo errado, o erro irmão é o CS0051 ("tipo de parâmetro … é menos acessível").
Qual usar? (regras práticas)
| O que é | Use | Por quê |
|---|---|---|
| Campos (os dados guardados) | private | Ninguém de fora estraga os dados. Para mostrar, use métodos ou propriedades (veja Encapsulamento). |
| Métodos que a classe oferece | public | São as portas de entrada. |
| Métodos ajudantes | private | Podem mudar ou sumir sem quebrar ninguém. |
| Coisas que as classes filhas precisam | protected | Abre só para a família. |
| Classes usadas só dentro do projeto | internal (ou nada) | Outros projetos não dependem delas. |
| Classes que outros projetos usam | public | É a "vitrine" da biblioteca. |
Na dúvida, comece com private. Abrir depois é fácil: o compilador avisa com CS0122 e você decide. Fechar depois é difícil: alguém pode já estar usando.
Quando dá errado
O que aconteceu: você usou um campo, método ou classe de um lugar que não tem acesso: um membro private de fora da classe, um protected de fora da família, ou um internal de outro projeto.
Como resolver: primeiro, procure a porta aberta que a classe oferece (um método public como Guardar). Se ela não existe e faz sentido existir, crie um método public que faça aquilo com regras. Trocar private por public num campo é o último recurso.
O que aconteceu: o método está sem modificador. Dentro de uma classe, isso quer dizer private.
Como resolver: escreva public na frente: public void Abrir(string tentativa).
O que aconteceu: uma classe solta no arquivo recebeu private, protected ou uma das combinações.
Como resolver: use public ou internal (ou nada, que é internal).
O que aconteceu: um membro public devolve (CS0050) ou recebe (CS0051) um tipo menos visível que ele.
Como resolver: deixe o tipo tão aberto quanto o método (public class Produto) ou feche o método e a classe dele.
Referência
| Escrita | Significado |
|---|---|
public | Qualquer código usa |
private | Só a própria classe usa. Padrão dos membros de classe |
internal | Só o mesmo projeto (assembly) usa. Padrão das classes soltas |
protected | A classe e as filhas |
protected internal | O mesmo projeto ou as filhas |
private protected C# 7.2 | As filhas que estão no mesmo projeto |
file class X C# 11 | Tipo visível só no próprio arquivo |
_nome | Convenção para campos private: _ + camelCase |
Missão final: o caixa da lojinha
Missão: escreva a classe Caixa com: (1) um campo private decimal _total; (2) um método public void Vender(decimal valor), que, se valor for menor ou igual a zero, chama Registrar($"Venda recusada: {valor:C}") e para, e senão soma no _total e chama Registrar($"Venda de {valor:C}"); (3) um método public decimal Total(), que devolve o _total; (4) um ajudante private void Registrar(string texto), que mostra [caixa] seguido do texto.
var caixa = new Caixa();
caixa.Vender(8m);
caixa.Vender(0m);
caixa.Vender(5.50m);
Console.WriteLine($"Total do dia: {caixa.Total():C}");
class Caixa
{
// campo, métodos públicos e o ajudante aqui
}
var caixa = new Caixa();
caixa.Vender(8m);
caixa.Vender(0m);
caixa.Vender(5.50m);
Console.WriteLine($"Total do dia: {caixa.Total():C}");
class Caixa
{
private decimal _total;
public void Vender(decimal valor)
{
if (valor <= 0)
{
Registrar($"Venda recusada: {valor:C}");
return;
}
_total += valor;
Registrar($"Venda de {valor:C}");
}
public decimal Total()
{
return _total;
}
private void Registrar(string texto)
{
Console.WriteLine($"[caixa] {texto}");
}
}
Exercícios
1. O mais fechado
Qual modificador deixa um campo visível só dentro da própria classe?
2. Sem modificador
Dentro de uma classe, você escreveu int _pontos;, sem modificador. Que modificador o C# aplica?
3. O código do erro
Qual é o código do erro "… é inacessível devido ao seu nível de proteção"? (só o código, como CS0000)
4. Uma classe escrita como class Produto { }, solta no arquivo, é:
5. Um campo protected da classe Animal pode ser usado por:
6. Um método public devolve um tipo internal. O que acontece?
Resumo
- Modificadores de acesso dizem quem pode usar cada coisa. Quem confere é o compilador, antes de o programa rodar.
public: todo mundo.private: só a classe.internal: o mesmo projeto.protected: a classe e as filhas.- Sem modificador, membro de classe é
privatee classe solta éinternal. Mesmo assim, escreva sempre o modificador. - Campos
private(com_nome), métodos que a classe oferecepublic, ajudantesprivate. Comece fechado e abra só o necessário. - Erros:
CS0122(sem acesso),CS1527(classe soltaprivate),CS0050/CS0051(acessibilidade inconsistente).