Menu

Modyfikatory dostępu w C#: public, private, protected i internal

Sześć modyfikatorów dostępu w C# (public, private, protected, internal, protected internal, private protected), na co każdy pozwala, jakie są wartości domyślne, gdy nie napiszesz żadnego, i jakie błędy kompilatora powodują.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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

ModyfikatorTyp zawierającyTyp pochodny, to samo assemblyInny kod, to samo assemblyTyp pochodny, inne assemblyInny kod, inne assembly
publictaktaktaktaktak
protected internaltaktaktaktaknie
protectedtaktaknietaknie
internaltaktaktaknienie
private protectedtaktaknienienie
privatetaknienienienie

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 internal to 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

GdzieDomyślnieDozwolone
Typ zadeklarowany w przestrzeni nazwinternalpublic, internal, file (C# 11)
Składowa klasyprivatewszystkie sześć
Składowa strukturyprivatepublic, internal, private (po strukturach nie można dziedziczyć)
Typ zagnieżdżonyprivatewszystkie sześć
Składowa interfejsupublicjawne modyfikatory dozwolone od C# 8
Wartość enumapublicż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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ