I modificatori di accesso decidono quale codice può usare un tipo o un membro. Sono il modo in cui una classe separa la parte su cui fa affidamento il resto del codice (la sua superficie pubblica) da quella che è libera di cambiare (i suoi dettagli interni). C# ne ha sei, più i valori predefiniti che ottieni quando non ne scrivi nessuno.
I sei modificatori
| Modificatore | Tipo contenitore | Tipo derivato, stesso assembly | Altro codice, stesso assembly | Tipo derivato, altro assembly | Altro codice, altro assembly |
|---|---|---|---|---|---|
public | sì | sì | sì | sì | sì |
protected internal | sì | sì | sì | sì | no |
protected | sì | sì | no | sì | no |
internal | sì | sì | sì | no | no |
private protected | sì | sì | no | no | no |
private | sì | no | no | no | no |
Un assembly è un progetto compilato: il .dll o .exe che produce. "Stesso assembly" significa "stesso progetto", ed è per questo che internal funziona come "visibile al mio codice, nascosto a chi usa la mia libreria".
private e public: l'incapsulamento
private è il valore predefinito per i membri e la scelta giusta per la maggior parte dei campi. La classe espone ciò che serve ai chiamanti tramite metodi e proprietà public, e quei metodi mantengono coerenti i dati dell'oggetto.
Output:
False
True
Priya: 180
Dato che balance è privato, gli unici modi per modificarlo sono Deposit e TryWithdraw, ed entrambi fanno rispettare le regole. Nessun altro codice del programma può creare un saldo negativo, in qualunque modo sia scritto. Ecco cosa ti dà l'incapsulamento: le regole stanno in un solo posto.
private vale per tipo, non per oggetto. Un metodo di Account può leggere other.balance su un'altra istanza di Account, ed è così che di solito si scrivono Equals e i metodi di confronto.
protected: visibile alle classi derivate
Un membro protected è nascosto al mondo esterno ma disponibile per le classi che ereditano dalla classe che lo dichiara.
Output:
Sam: 4000.00
Rita: 6400.00
C'è una regola che sorprende molti. Dentro Manager puoi usare baseSalary su this o su un altro Manager, ma non su un Employee qualsiasi:
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)
}
}
Il motivo: other potrebbe essere un Contractor che deriva anch'esso da Employee, e protected concede l'accesso al tuo ramo della gerarchia, non a quello dei tuoi fratelli.
Un campo protected lega per sempre ogni classe derivata a quel campo. Molti progetti preferiscono una proprietà o un metodo protected, oppure tengono i campi privati e danno alle classi derivate solo ciò che serve.
internal: visibile dentro l'assembly
I membri e i tipi internal possono essere usati da qualsiasi codice dello stesso progetto e da niente al di fuori. Una libreria lo usa per le funzioni di supporto su cui si basano le sue classi pubbliche:
// 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;
}
I tipi di primo livello sono internal quando non scrivi alcun modificatore. Ecco perché una classe che hai dimenticato di marcare public in una libreria di classi "manca" nel progetto che la referenzia.
Anche i progetti di unit test sono assembly separati, quindi nemmeno loro vedono il codice internal. La soluzione standard è un attributo nella libreria:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]
protected internal e private protected
Questi due combinano i modificatori precedenti, in modi opposti, e i loro nomi si confondono facilmente:
protected internalè protected oppure internal: qualsiasi codice nello stesso assembly, più le classi derivate in altri assembly. È il più ampio dei due.private protected(C# 7.2) è protected e internal: solo le classi derivate che si trovano anche nello stesso assembly. È il più ristretto, per una classe base i cui punti di estensione devono restare dentro la tua libreria.
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)
}
Valori predefiniti quando non scrivi nulla
| Dove | Predefinito | Ammessi |
|---|---|---|
| Tipo dichiarato in un namespace | internal | public, internal, file (C# 11) |
| Membro di una classe | private | tutti e sei |
| Membro di una struct | private | public, internal, private (le struct non si possono ereditare) |
| Tipo annidato | private | tutti e sei |
| Membro di un'interfaccia | public | modificatori espliciti ammessi da C# 8 |
| Membro di un enum | public | nessuno |
Essere espliciti costa una parola e toglie un dubbio al prossimo lettore, per questo la maggior parte delle guide di stile chiede il modificatore anche quando coincide con quello predefinito.
Accesso a livello di accessor
Una proprietà o un indicizzatore può dare a uno dei suoi accessor un modificatore più ristretto di quello della proprietà stessa:
Output:
report.pdf: 100%
Il modificatore dell'accessor deve essere più restrittivo di quello della proprietà, e solo uno dei due accessor può averne uno.
Accessibilità incoerente
Un membro pubblico non può esporre un tipo meno visibile di sé, perché i chiamanti riceverebbero qualcosa che non possono nominare:
internal class Discount { }
public class Checkout
{
public Discount Current() => null;
// error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}
La stessa regola dà CS0051 per i tipi dei parametri e CS0053 per i tipi delle proprietà. Risolvi rendendo il tipo visibile quanto il membro, oppure il membro nascosto quanto il tipo.
Scegliere un modificatore
Parti dal modificatore più restrittivo che funziona e allargalo solo quando un chiamante ne ha bisogno. Rendere qualcosa più visibile in seguito è facile e non rompe mai nessuno; renderlo meno visibile rompe ogni chiamante che lo usava. In pratica: campi private, i membri che costituiscono lo scopo della classe public, le funzioni di supporto private e i tipi riservati alla libreria internal.
Domande frequenti
Quali sono i modificatori di accesso in C#?
Sono sei: public (qualsiasi codice), private (solo il tipo che lo contiene), protected (il tipo che lo contiene e i tipi derivati), internal (qualsiasi codice nello stesso assembly), protected internal (stesso assembly, oppure tipi derivati ovunque) e private protected (tipi derivati nello stesso assembly, C# 7.2). C# 11 ha aggiunto file per i tipi visibili in un solo file sorgente.
Qual è il modificatore di accesso predefinito in C#?
I membri di una classe o di una struct (campi, metodi, proprietà, tipi annidati) sono private per impostazione predefinita. I tipi di primo livello (una classe dichiarata direttamente in un namespace) sono internal per impostazione predefinita. I membri delle interfacce e degli enum sono pubblici.
Cosa significa protected in C#?
Un membro protected è visibile nella sua classe e in qualsiasi classe derivata, ma non al resto del codice. Una classe derivata può usarlo solo attraverso il proprio tipo: dentro Manager, this.baseSalary funziona, ma accedere a baseSalary su un altro oggetto Employee dà l'errore CS1540.
Cos'è internal in C#?
internal rende un tipo o un membro visibile a tutto il codice dello stesso assembly (il .dll o .exe compilato dello stesso progetto) e invisibile al di fuori. È il modo in cui una libreria tiene le classi di supporto fuori dalla sua API pubblica. [assembly: InternalsVisibleTo("MyLib.Tests")] permette anche a un progetto di test di vederle.
Come risolvo "is inaccessible due to its protection level"?
È l'errore CS0122: il codice sta usando un membro che non gli è permesso vedere, quasi sempre un campo rimasto al valore predefinito private. Se il codice esterno deve leggerlo, esponi una proprietà o un metodo pubblico; non rendere semplicemente pubblico il campo. Se è un tipo di un altro progetto, probabilmente è internal.