Os modificadores de acesso decidem que código pode usar um tipo ou um membro. É com eles que uma classe separa a parte de que outro código depende (sua superfície pública) da parte que ela é livre para mudar (seus detalhes internos). C# tem seis deles, mais os padrões que você recebe quando não escreve nenhum.
Os seis modificadores
| Modificador | Tipo que o contém | Tipo derivado, mesmo assembly | Outro código, mesmo assembly | Tipo derivado, outro assembly | Outro código, outro assembly |
|---|---|---|---|---|---|
public | sim | sim | sim | sim | sim |
protected internal | sim | sim | sim | sim | não |
protected | sim | sim | não | sim | não |
internal | sim | sim | sim | não | não |
private protected | sim | sim | não | não | não |
private | sim | não | não | não | não |
Um assembly é um projeto compilado: o .dll ou .exe que ele produz. "Mesmo assembly" significa "mesmo projeto", e é por isso que internal funciona como "visível para o meu código, escondido de quem usa a minha biblioteca".
private e public: encapsulamento
private é o padrão para membros e a escolha certa para a maioria dos campos. A classe expõe o que quem chama precisa por meio de métodos e propriedades public, e esses métodos mantêm os dados do objeto consistentes.
Saída:
False
True
Priya: 180
Como balance é privado, as únicas formas de mudá-lo são Deposit e TryWithdraw, e as duas aplicam as regras. Nenhum outro código do programa consegue criar um saldo negativo, não importa como seja escrito. É isso que o encapsulamento compra: as regras ficam em um único lugar.
private vale por tipo, não por objeto. Um método de Account pode ler other.balance em outra instância de Account, e é assim que Equals e os métodos de comparação costumam ser escritos.
protected: visível para as classes derivadas
Um membro protected fica escondido do mundo de fora, mas disponível para as classes que herdam da classe que o declara.
Saída:
Sam: 4000.00
Rita: 6400.00
Há uma regra que surpreende. Dentro de Manager, você pode usar baseSalary em this ou em outro Manager, mas não em um Employee qualquer:
class Manager : Employee
{
public decimal Compare(Employee other)
{
return baseSalary - other.baseSalary;
// error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
// of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
}
}
O motivo: other poderia ser um Contractor que também deriva de Employee, e protected dá acesso ao seu próprio ramo da hierarquia, não ao dos seus irmãos.
Um campo protected acopla para sempre todas as classes derivadas a esse campo. Muitos códigos preferem uma propriedade ou um método protected, ou mantêm os campos privados e dão às classes derivadas só o que elas precisam.
internal: visível dentro do assembly
Membros e tipos internal podem ser usados por qualquer código do mesmo projeto e por nada fora dele. Uma biblioteca o usa para os auxiliares de que suas classes públicas dependem:
// In the MyShop.Pricing library project
public class PriceCalculator
{
public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}
internal static class TaxRules // callers of the library cannot see this class
{
internal static decimal Apply(decimal amount) => amount * 1.2m;
}
Tipos de nível superior são internal quando você não escreve nenhum modificador. É por isso que uma classe que você esqueceu de marcar como public em uma biblioteca de classes "some" do projeto que a referencia.
Projetos de testes unitários são assemblies separados, então também não enxergam código internal. A solução padrão é um atributo na biblioteca:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]
protected internal e private protected
Esses dois combinam os modificadores anteriores, de formas opostas, e os nomes são fáceis de confundir:
protected internalé protected ou internal: qualquer código no mesmo assembly, mais as classes derivadas em outros assemblies. É o mais amplo.private protected(C# 7.2) é protected e internal: só as classes derivadas que também estão no mesmo assembly. É o mais restrito, para uma classe base cujos pontos de extensão devem ficar dentro da sua biblioteca.
public class ReportBase
{
protected internal string Title; // same assembly, or subclasses anywhere
private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}
Padrões quando você não escreve nada
| Onde | Padrão | Permitidos |
|---|---|---|
| Tipo declarado em um namespace | internal | public, internal, file (C# 11) |
| Membro de uma classe | private | todos os seis |
| Membro de uma struct | private | public, internal, private (structs não podem ser herdadas) |
| Tipo aninhado | private | todos os seis |
| Membro de interface | public | modificadores explícitos permitidos desde o C# 8 |
| Membro de enum | public | nenhum |
Ser explícito custa uma palavra e tira uma dúvida do próximo leitor, então a maioria dos guias de estilo pede o modificador mesmo quando ele é igual ao padrão.
Acesso por acessor
Uma propriedade ou um indexador pode dar a um dos acessores um modificador mais restrito que o da própria propriedade:
Saída:
report.pdf: 100%
O modificador do acessor precisa ser mais restritivo que o da propriedade, e só um dos dois acessores pode ter um.
Acessibilidade inconsistente
Um membro público não pode expor um tipo menos visível que ele mesmo, porque quem chama receberia algo que não tem permissão para nomear:
internal class Discount { }
public class Checkout
{
public Discount Current() => null;
// error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}
A mesma regra dá CS0051 para tipos de parâmetros e CS0053 para tipos de propriedades. Resolva tornando o tipo tão visível quanto o membro, ou o membro tão escondido quanto o tipo.
Escolhendo um modificador
Comece com o modificador mais restritivo que funciona e amplie-o só quando alguém precisar. Tornar algo mais visível depois é fácil e nunca quebra ninguém; tornar algo menos visível quebra todos que o usavam. Na prática: campos private, os membros que formam o propósito da classe public, auxiliares private e tipos só da biblioteca internal.
Perguntas frequentes
Quais são os modificadores de acesso em C#?
São seis: public (qualquer código), private (só o tipo que o contém), protected (o tipo que o contém e os tipos derivados dele), internal (qualquer código no mesmo assembly), protected internal (mesmo assembly, ou tipos derivados em qualquer lugar) e private protected (tipos derivados no mesmo assembly, C# 7.2). O C# 11 adicionou file para tipos visíveis em um único arquivo-fonte.
Qual é o modificador de acesso padrão em C#?
Membros de uma classe ou struct (campos, métodos, propriedades, tipos aninhados) são private por padrão. Tipos de nível superior (uma classe declarada direto em um namespace) são internal por padrão. Membros de interfaces e de enums são públicos.
O que protected significa em C#?
Um membro protected é visível dentro da própria classe e dentro de qualquer classe derivada dela, mas não para outro código. Uma classe derivada só pode usá-lo por meio do próprio tipo: dentro de Manager, this.baseSalary funciona, mas acessar baseSalary em outro objeto Employee qualquer é o erro CS1540.
O que é internal em C#?
internal torna um tipo ou membro visível para todo o código no mesmo assembly (o .dll ou .exe compilado do mesmo projeto) e invisível fora dele. É assim que uma biblioteca mantém classes auxiliares fora da sua API pública. [assembly: InternalsVisibleTo("MyLib.Tests")] permite que um projeto de testes também as veja.
Como resolver "is inaccessible due to its protection level"?
Esse é o erro CS0122: o código está usando um membro que não tem permissão para ver, na maioria das vezes um campo deixado no padrão private. Se o código de fora deve lê-lo, exponha uma propriedade ou um método públicos; não torne o campo público simplesmente. Se for um tipo de outro projeto, ele provavelmente é internal.