Modyfikatory dostępu decydują, który kod może używać typu lub składowej. Dzięki nim klasa oddziela część, na której polega inny kod (swoją publiczną powierzchnię), od części, którą może swobodnie zmieniać (swoich wnętrzności). C# ma ich sześć, do tego dochodzą wartości domyślne, obowiązujące, gdy nie napiszesz żadnego.
Sześć modyfikatorów
| Modyfikator | Typ zawierający | Typ pochodny, to samo assembly | Inny kod, to samo assembly | Typ pochodny, inne assembly | Inny kod, inne assembly |
|---|---|---|---|---|---|
public | tak | tak | tak | tak | tak |
protected internal | tak | tak | tak | tak | nie |
protected | tak | tak | nie | tak | nie |
internal | tak | tak | tak | nie | nie |
private protected | tak | tak | nie | nie | nie |
private | tak | nie | nie | nie | nie |
Assembly to jeden skompilowany projekt: plik .dll lub .exe, który powstaje w wyniku kompilacji. "To samo assembly" oznacza "ten sam projekt", dlatego internal działa jak "widoczne dla mojego kodu, ukryte przed użytkownikami mojej biblioteki".
private i public: hermetyzacja
private to wartość domyślna dla składowych i właściwy wybór dla większości pól. Klasa udostępnia to, czego potrzebuje kod wywołujący, przez metody i właściwości public, a te metody dbają o spójność danych obiektu.
Wynik:
False
True
Priya: 180
Ponieważ balance jest prywatne, zmienić je można tylko przez Deposit i TryWithdraw, a obie metody pilnują reguł. Żaden inny kod w programie nie utworzy ujemnego salda, niezależnie od tego, jak jest napisany. To właśnie daje hermetyzacja: reguły są w jednym miejscu.
private działa na poziomie typu, a nie obiektu. Metoda klasy Account może odczytać other.balance z innej instancji Account, i tak zwykle pisze się Equals oraz metody porównujące.
protected: widoczne dla klas pochodnych
Składowa protected jest ukryta przed światem zewnętrznym, ale dostępna dla klas, które dziedziczą po klasie deklarującej.
Wynik:
Sam: 4000.00
Rita: 6400.00
Jest jedna reguła, która zaskakuje. Wewnątrz Manager możesz używać baseSalary na this albo na innym obiekcie Manager, ale nie na dowolnym Employee:
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)
}
}
Powód: other może być obiektem Contractor, który też dziedziczy po Employee, a protected daje dostęp do własnej gałęzi hierarchii, a nie do gałęzi rodzeństwa.
Pole protected na zawsze wiąże każdą klasę pochodną z tym polem. Wiele projektów woli właściwość lub metodę protected albo trzyma pola jako prywatne i daje klasom pochodnym tylko to, czego potrzebują.
internal: widoczne wewnątrz assembly
Składowe i typy internal może używać dowolny kod w tym samym projekcie i nic poza nim. Biblioteka używa tego modyfikatora dla klas pomocniczych, na których opierają się jej klasy publiczne:
// 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;
}
Typy najwyższego poziomu są internal, gdy nie napiszesz żadnego modyfikatora. Dlatego klasa w bibliotece, której zabrakło oznaczenia public, "znika" z projektu, który się do tej biblioteki odwołuje.
Projekty testów jednostkowych to osobne assembly, więc też nie widzą kodu internal. Standardowe rozwiązanie to atrybut w bibliotece:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]
protected internal i private protected
Te dwa modyfikatory łączą poprzednie, na odwrotne sposoby, a ich nazwy łatwo pomylić:
protected internalto protected lub internal: dowolny kod w tym samym assembly plus klasy pochodne w innych assembly. To ten szerszy.private protected(C# 7.2) to protected i internal: tylko klasy pochodne, które są też w tym samym assembly. To ten węższy, dla klasy bazowej, której punkty rozszerzeń mają zostać wewnątrz twojej biblioteki.
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)
}
Wartości domyślne, gdy nic nie napiszesz
| Gdzie | Domyślnie | Dozwolone |
|---|---|---|
| Typ zadeklarowany w przestrzeni nazw | internal | public, internal, file (C# 11) |
| Składowa klasy | private | wszystkie sześć |
| Składowa struktury | private | public, internal, private (po strukturach nie można dziedziczyć) |
| Typ zagnieżdżony | private | wszystkie sześć |
| Składowa interfejsu | public | jawne modyfikatory dozwolone od C# 8 |
| Wartość enuma | public | żadne |
Jawny zapis kosztuje jedno słowo i oszczędza pytań kolejnej osobie czytającej kod, dlatego większość przewodników stylu wymaga modyfikatora nawet wtedy, gdy pokrywa się z domyślnym.
Dostęp na poziomie akcesora
Właściwość lub indekser może nadać jednemu akcesorowi węższy modyfikator niż cała właściwość:
Wynik:
report.pdf: 100%
Modyfikator akcesora musi być bardziej restrykcyjny niż modyfikator właściwości i może go mieć tylko jeden z dwóch akcesorów.
Niespójna dostępność
Publiczna składowa nie może udostępniać typu, który jest mniej widoczny niż ona sama, bo kod wywołujący dostałby coś, czego nie wolno mu nawet nazwać:
internal class Discount { }
public class Checkout
{
public Discount Current() => null;
// error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}
Ta sama reguła daje CS0051 dla typów parametrów i CS0053 dla typów właściwości. Napraw to, czyniąc typ tak samo widocznym jak składowa albo składową tak samo ukrytą jak typ.
Wybór modyfikatora
Zacznij od najbardziej restrykcyjnego modyfikatora, który działa, i rozszerzaj go tylko wtedy, gdy potrzebuje tego kod wywołujący. Zwiększenie widoczności później jest łatwe i nikomu niczego nie psuje; zmniejszenie widoczności psuje każdy kod, który z niej korzystał. W praktyce: pola private, składowe stanowiące cel klasy public, metody pomocnicze private, a typy przeznaczone tylko dla biblioteki internal.
Najczęściej zadawane pytania
Jakie są modyfikatory dostępu w C#?
Jest ich sześć: public (dowolny kod), private (tylko typ, który zawiera składową), protected (typ zawierający i typy z niego pochodne), internal (dowolny kod w tym samym assembly), protected internal (to samo assembly albo typy pochodne w dowolnym miejscu) i private protected (typy pochodne w tym samym assembly, C# 7.2). C# 11 dodał file dla typów widocznych tylko w jednym pliku źródłowym.
Jaki jest domyślny modyfikator dostępu w C#?
Składowe klasy lub struktury (pola, metody, właściwości, typy zagnieżdżone) są domyślnie private. Typy najwyższego poziomu (klasa zadeklarowana bezpośrednio w przestrzeni nazw) są domyślnie internal. Składowe interfejsów i wartości enumów są publiczne.
Co oznacza protected w C#?
Składowa protected jest widoczna w swojej klasie i w każdej klasie pochodnej, ale nie dla innego kodu. Klasa pochodna może z niej korzystać tylko przez własny typ: wewnątrz Manager zapis this.baseSalary działa, ale sięgnięcie do baseSalary w innym obiekcie Employee to błąd CS1540.
Czym jest internal w C#?
internal sprawia, że typ lub składowa jest widoczna dla całego kodu w tym samym assembly (skompilowanym pliku .dll lub .exe tego samego projektu) i niewidoczna poza nim. W ten sposób biblioteka trzyma klasy pomocnicze poza swoim publicznym API. [assembly: InternalsVisibleTo("MyLib.Tests")] pozwala zobaczyć je także projektowi testów.
Jak naprawić błąd "is inaccessible due to its protection level"?
To błąd CS0122: kod używa składowej, której nie wolno mu widzieć, najczęściej pola pozostawionego z domyślnym private. Jeśli kod zewnętrzny ma go odczytywać, udostępnij publiczną właściwość lub metodę; nie zmieniaj po prostu pola na publiczne. Jeśli to typ z innego projektu, prawdopodobnie jest internal.