Modyfikatory dostępu — kto może użyć elementu
Automat udostępnia przyciski, ale nie pozwala przechodniom przestawiać licznika sprzedaży. Klasa z publicznym polem obok metody, która to pole sprawdza, nie pilnuje niczego — wystarczy jedno przypisanie, żeby ominąć wszystkie reguły.
Czego się dziś nauczysz
- Wskażesz, kto ma dostęp do składowej oznaczonej każdym z czterech modyfikatorów.
- Podasz regułę domyślną dla składowej i dla klasy.
- Zamkniesz pole i udostępnisz zamiast niego metodę.
- Wyjaśnisz, dlaczego obiekt widzi prywatne pola innego obiektu tej samej klasy.
- Rozpoznasz komunikat CS0122 i będziesz wiedział, co go powoduje.
Przygotowanie: lekcje 01–38. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Co się dzieje, gdy wszystko jest publiczne
Klasa Konto ma metodę pilnującą, żeby nie dało się wypłacić więcej, niż jest na koncie:
class Konto
{
public decimal Saldo;
public void Wyplata(decimal kwota)
{
if (kwota > Saldo)
{
Console.WriteLine("Za malo srodkow");
return;
}
Saldo -= kwota;
}
}
Konto k = new Konto();
k.Saldo = 100;
k.Wyplata(500); // Za malo srodkow - dziala
k.Saldo = -9000; // ...i tyle z pilnowania
Sprawdzenie w metodzie nic nie daje, jeśli obok stoi otwarte pole, które każdy może ustawić na dowolną wartość. Obiekt nie panuje nad własnym stanem.
Modyfikator dostępu pozwala powiedzieć, co jest wejściem dla innych, a co wewnętrznym mechanizmem.
class Konto
{
private decimal saldo; // tego nikt z zewnatrz nie tknie
public void Wplata(decimal kwota)
{
if (kwota <= 0) return;
saldo += kwota;
}
public void Wyplata(decimal kwota)
{
if (kwota <= 0 || kwota > saldo)
{
Console.WriteLine("Operacja odrzucona");
return;
}
saldo -= kwota;
}
public decimal Stan()
{
return saldo;
}
}
Konto k = new Konto();
k.Wplata(100);
k.Wyplata(500); // Operacja odrzucona
// k.saldo = -9000; // BLAD CS0122: 'Konto.saldo' jest niedostepne
Console.WriteLine(k.Stan()); // 100
Obudowa automatu
Automat z napojami ma przyciski i otwór na monety — to jego część publiczna. Licznik sprzedaży i kasa są w środku, za obudową. Nie dlatego, że to tajemnica, tylko dlatego, że przechodzień, który by je przestawiał, popsułby działanie całości. Klasa działa tak samo.
Cztery słowa i to, co naprawdę znaczą
| Modyfikator | Kto ma dostęp | Typowe użycie |
|---|---|---|
public | każdy kod, także z innego projektu | metody, które są wejściem do klasy |
private | tylko wnętrze tej samej klasy | pola i metody pomocnicze — domyślny wybór |
internal | cały kod w tym samym projekcie | elementy wspólne dla projektu, ukryte przed innymi |
protected | ta klasa i klasy, które po niej dziedziczą | elementy dla klas pochodnych — lekcja 45 |
Co się dzieje, gdy nic nie napiszesz
Składowa klasy bez modyfikatora jest private. Sama klasa bez modyfikatora jest internal. Warto pisać modyfikator jawnie — czytelnik nie musi wtedy pamiętać dwóch różnych reguł domyślnych.
class Robot // domyslnie internal
{
int energia; // domyslnie private
private int przejechane; // to samo, tylko widac
public void Rusz() { } // widoczne z zewnatrz
private void ZuzyjEnergie() { } // mechanizm wewnetrzny
}
Zasada, od której zaczynamy
Domyślnie wszystko jest private. Publiczne robimy dopiero to, czego ktoś z zewnątrz naprawdę potrzebuje — i dopiero wtedy, gdy wiemy, po co.
Łatwiej otworzyć niż zamknąć
Pole prywatne można w każdej chwili upublicznić i nic się nie zepsuje. Publiczne, z którego już ktoś korzysta, trudno zamknąć — trzeba poprawić wszystkie miejsca użycia. Dlatego zaczynamy od zamkniętego, a otwieramy na żądanie.
Prywatne znaczy „dla tej klasy”, nie „dla tego obiektu”
To zaskakuje niemal każdego przy pierwszym spotkaniu. Metoda obiektu widzi prywatne pola innego obiektu tej samej klasy:
class Gracz
{
private int punkty;
public void Dodaj(int ile)
{
punkty += ile;
}
public bool WygrywaZ(Gracz inny)
{
return punkty > inny.punkty; // siegamy do prywatnego pola INNEGO obiektu
}
}
To poprawny kod. private ogranicza dostęp do typu, a nie do pojedynczego egzemplarza — a klasa ma prawo znać wnętrze swoich obiektów. Bez tego nie dałoby się napisać porównania dwóch graczy bez otwierania pola na cały świat.
Dostęp nie zależy od pliku
Klasa Program zapisana w tym samym pliku co Konto nie widzi jego prywatnych pól. Liczy się granica klasy, a nie granica pliku. Komunikat CS0122 pojawi się także wtedy, gdy obie klasy stoją obok siebie w jednym Program.cs.
Metody prywatne
Prywatne bywają nie tylko pola. Metoda pomocnicza, która jest krokiem w środku większej czynności, nie musi być widoczna na zewnątrz:
class Raport
{
public string Zbuduj(decimal kwota)
{
return $"{Naglowek()}\nDo zaplaty: {Sformatuj(kwota)}";
}
private string Naglowek()
{
return "RACHUNEK";
}
private string Sformatuj(decimal kwota)
{
return $"{kwota:F2} zl";
}
}
Użytkownik klasy wywołuje jedną metodę Zbuduj. Pozostałe dwie są szczegółem, który wolno zmienić bez pytania nikogo o zgodę.
internal, czyli granica projektu
internal otwiera składową dla całego projektu, ale zamyka ją przed kodem z zewnątrz. W programie konsolowym złożonym z jednego projektu zachowuje się dokładnie tak samo jak public — różnica pojawia się dopiero wtedy, gdy rozwiązanie ma kilka projektów, na przykład bibliotekę i korzystającą z niej aplikację.
// projekt: Biblioteka
public class Kalkulator // widoczna dla innych projektow
{
public int Dodaj(int a, int b) => a + b;
internal int LicznikWywolan; // widoczne tylko w projekcie Biblioteka
}
A klasy zaprzyjaźnione?
W C++ słowo friend pozwala wskazanej klasie lub funkcji sięgnąć do prywatnych składowych. C# nie ma takiego mechanizmu — i celowo, bo łamie on hermetyzację. Najbliższym odpowiednikiem jest właśnie internal. Przy testach jednostkowych z lekcji 56 dodaje się czasem wpis InternalsVisibleTo w pliku projektu, żeby projekt testowy widział składowe internal — to jedyne miejsce, w którym C# zbliża się do idei zaprzyjaźnienia.
protected na zapowiedź
Czwarty modyfikator, protected, otwiera składową dla klas dziedziczących po tej klasie. Dopóki nie ma dziedziczenia, nie ma po nim widocznej różnicy względem private. Wracamy do niego w lekcji 45.
Przykład: automat, którego nie da się oszukać
class Automat
{
private decimal kasa = 0m;
private int sprzedane = 0;
private decimal wrzucone = 0m;
private readonly decimal cena;
public Automat(decimal cena)
{
this.cena = cena;
}
public void Wrzuc(decimal kwota)
{
if (!Poprawna(kwota))
{
Console.WriteLine($"Odrzucono monete {kwota:F2}");
return;
}
wrzucone += kwota;
Console.WriteLine($"Wrzucono {kwota:F2}, razem {wrzucone:F2}");
}
public void Kup()
{
if (wrzucone < cena)
{
Console.WriteLine($"Brakuje {cena - wrzucone:F2}");
return;
}
decimal reszta = wrzucone - cena;
kasa += cena;
sprzedane++;
wrzucone = 0m;
Console.WriteLine($"Napoj wydany, reszta {reszta:F2}");
}
public string Raport()
{
return $"Sprzedano: {sprzedane}, w kasie: {kasa:F2} zl";
}
private bool Poprawna(decimal kwota)
{
return kwota == 0.50m || kwota == 1m || kwota == 2m || kwota == 5m;
}
}
class Program
{
static void Main()
{
Automat a = new Automat(3.50m);
a.Wrzuc(2m);
a.Wrzuc(0.30m); // nie ma takiej monety
a.Kup(); // za malo
a.Wrzuc(2m);
a.Kup();
Console.WriteLine();
Console.WriteLine(a.Raport());
// a.kasa = 1000m; // CS0122 - niedostepne
// a.Poprawna(0.10m); // CS0122 - metoda prywatna
}
}
Wrzucono 2,00, razem 2,00
Odrzucono monete 0,30
Brakuje 1,50
Wrzucono 2,00, razem 4,00
Napoj wydany, reszta 0,50
Sprzedano: 1, w kasie: 3,50 zl
Co dzieje się po kolei
- Wszystkie cztery pola są prywatne. Z zewnątrz dostępne są tylko trzy czynności: wrzuć, kup, pokaż raport. Tyle wystarczy do korzystania z automatu.
Poprawnajest prywatna, bo to wewnętrzna reguła automatu. Gdy lista akceptowanych monet się zmieni, nikt poza tą klasą tego nie zauważy.- Po sprzedaży
wrzuconewraca do zera, akasaisprzedanerosną. Trzy pola zmieniają się razem i zawsze w komplecie — bo zmienia je jedna metoda. - Dwa zakomentowane wiersze na końcu to sedno lekcji: kod spoza klasy nie ma jak zepsuć jej stanu. Odkomentuj je i przeczytaj komunikat CS0122.
cenajestreadonlyi prywatna — ustalona przy tworzeniu automatu, niezmienna przez cały czas jego istnienia.
Zestawienie elementów
| Element | Zasięg | Uwagi |
|---|---|---|
public | wszędzie | Wejście do klasy. Każda składowa publiczna to obietnica, którą trudno potem wycofać. |
private | wnętrze tej samej klasy | Domyślny wybór dla pól i metod pomocniczych. Obowiązuje na poziomie klasy, nie obiektu. |
internal | ten sam projekt | W programie z jednym projektem nieodróżnialne od public. |
protected | ta klasa i klasy pochodne | Sens pojawia się dopiero przy dziedziczeniu (lekcja 45). |
| brak modyfikatora przy składowej | jak private | Domyślna reguła dla pól i metod w klasie. |
| brak modyfikatora przy klasie | jak internal | Inna reguła domyślna niż dla składowych — stąd zamieszanie. |
CS0122 | — | „Składowa jest niedostępna ze względu na poziom ochrony” — to komunikat tej lekcji. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
| Publiczne pole obok metody, która je sprawdza | Sprawdzenie da się ominąć jednym przypisaniem. Albo pole jest prywatne, albo sprawdzanie nie ma sensu. |
Wszystko public „na wszelki wypadek” | Każda składowa publiczna wiąże ci ręce przy późniejszych zmianach. Otwieraj to, co potrzebne, nie wszystko. |
Wszystko private, także metody wejściowe | Klasa, z której nie da się skorzystać. Publiczne ma być to, co stanowi jej przeznaczenie. |
Oczekiwanie, że private chroni przed innym obiektem tej samej klasy | Nie chroni — dostęp obowiązuje na poziomie klasy. To celowe i potrzebne przy porównaniach. |
| Liczenie na to, że wspólny plik daje dostęp | Granicą jest klasa, nie plik. CS0122 pojawi się też w jednym Program.cs. |
protected w klasie bez dziedziczenia | Kompiluje się, ale zachowuje się jak private i myli czytelnika. |
Zadania
Weź klasę Konto z publicznym polem Saldo i metodą Wyplata. Ustaw saldo z Main na wartość ujemną i pokaż, że program to przyjmuje. Następnie zmień pole na prywatne, dodaj metodę Stan() i zapisz treść błędu, który pojawia się teraz przy próbie zmiany salda.
Napisz klasę z czterema składowymi: polem bez modyfikatora, polem private, metodą bez modyfikatora i metodą public. Z Main spróbuj użyć każdej z nich i zapisz, które próby się nie kompilują.
Napisz klasę Haslo z metodą publiczną bool Poprawne(string tekst) oraz trzema metodami prywatnymi sprawdzającymi po jednym warunku: długość co najmniej osiem znaków, obecność cyfry i obecność wielkiej litery. Publiczna metoda ma tylko łączyć wyniki trzech prywatnych.
W jednym projekcie utwórz klasę Licznik z polem private int wartosc, metodą internal void Zwieksz() i metodą public int Odczytaj(). Z Main spróbuj sięgnąć po każdą z nich, zapisz, która próba się nie kompiluje, i wyjaśnij w komentarzu, dlaczego internal zachowuje się tutaj tak samo jak public.
Napisz klasę Gracz z prywatnym polem punktów, metodą Dodaj oraz metodą bool WygrywaZ(Gracz inny) sięgającą do prywatnego pola przekazanego gracza. Uruchom program i wyjaśnij w komentarzu, dlaczego to się kompiluje, mimo że pole jest prywatne.
Napisz klasę Sejf z prywatnym kodem ustawianym w konstruktorze i prywatnym licznikiem nieudanych prób. Metoda bool Otworz(string kod) ma otwierać sejf przy poprawnym kodzie i zerować licznik, a przy trzech błędnych próbach z rzędu blokować sejf na stałe — także przed poprawnym kodem. Z zewnątrz ma być dostępne wyłącznie otwieranie i sprawdzenie, czy sejf jest zablokowany. Sprawdź program na czterech scenariuszach, w tym na poprawnym kodzie podanym po blokadzie.
Co trzeba zapamiętać
privatezamyka składową we wnętrzu klasy,publicotwiera ją dla każdego.- Składowa bez modyfikatora jest prywatna, klasa bez modyfikatora —
internal. - Zaczynamy od prywatnego; publiczne robimy to, co ma być wejściem do klasy.
- Publiczne pole obok metody sprawdzającej sprawia, że sprawdzenie da się ominąć.
privatedziała na poziomie klasy — obiekt widzi prywatne pola innego obiektu tej samej klasy.- Granicą dostępu jest klasa, a nie plik; CS0122 pojawia się także w jednym pliku.
internalotwiera składową w obrębie projektu i jest najbliższym odpowiednikiemfriendz C++.
Dokumentacja: Microsoft Learn — temat tej lekcji.