Static — pola, metody, klasy i konstruktor statyczny
Każdy bilet ma własny numer, ale licznik wydanych numerów musi być jeden na całą kasę. To dobry moment, żeby rozdzielić dane obiektu od danych typu.
Czego się dziś nauczysz
- Rozstrzygniesz, czy dana opisuje obiekt, czy całą klasę.
- Zadeklarujesz pole i metodę statyczną oraz odwołasz się do nich poprawnie.
- Wyjaśnisz komunikaty CS0176 i CS0120.
- Napiszesz klasę statyczną z metodami pomocniczymi.
- Odróżnisz
constodstatic readonly.
Przygotowanie: lekcje 01–43. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Licznik, który musi być jeden
Każdy bilet ma własny numer. Ale numer bierze się z licznika, który musi być wspólny dla wszystkich biletów — inaczej każdy zaczyna od jedynki.
class Bilet
{
private int licznik = 0; // pole obiektu - kazdy ma swoje
public int Numer { get; }
public Bilet()
{
licznik++;
Numer = licznik;
}
}
Console.WriteLine(new Bilet().Numer); // 1
Console.WriteLine(new Bilet().Numer); // 1 - znowu jeden!
Console.WriteLine(new Bilet().Numer); // 1
Każdy nowy bilet dostaje własny, świeży licznik. Potrzebujemy czegoś, co należy do typu, a nie do obiektu:
class Bilet
{
private static int licznik = 0; // JEDNO pole dla calej klasy
public int Numer { get; }
public Bilet()
{
licznik++;
Numer = licznik;
}
public static int LiczbaWydanych => licznik;
}
Console.WriteLine(new Bilet().Numer); // 1
Console.WriteLine(new Bilet().Numer); // 2
Console.WriteLine(new Bilet().Numer); // 3
Console.WriteLine(Bilet.LiczbaWydanych); // 3
Jedno pytanie rozstrzyga wszystko
„Czy ta wartość opisuje konkretny obiekt, czy wszystkie obiekty tej klasy?” Numer biletu opisuje jeden bilet — to pole obiektu. Liczba wydanych biletów opisuje całość — to pole static.
Odwołanie przez nazwę typu
Bilet b = new Bilet();
Console.WriteLine(b.Numer); // pole obiektu - przez zmienna
Console.WriteLine(Bilet.LiczbaWydanych); // skladowa statyczna - przez TYP
// Console.WriteLine(b.LiczbaWydanych); // BLAD CS0176
Składowej statycznej nie odczytujemy przez zmienną obiektową. To nie jest kwestia stylu — kompilator odrzuca taki zapis komunikatem CS0176.
| Składowa obiektu | Składowa static | |
|---|---|---|
| Ile egzemplarzy | po jednym na obiekt | jeden na całą klasę |
| Odwołanie | obiekt.Nazwa | Klasa.Nazwa |
| Widzi pola obiektu | tak | nie — nie wie, o który obiekt chodzi |
| Widzi pola statyczne | tak | tak |
| Powstaje | przy new | przy pierwszym użyciu klasy |
class Bilet
{
private static int licznik;
private int numer;
public static void Zeruj()
{
licznik = 0; // wolno - pole statyczne
// numer = 0; // BLAD CS0120 - ktorego biletu numer?
}
public void Pokaz()
{
Console.WriteLine(numer); // wolno - pole tego obiektu
Console.WriteLine(licznik); // wolno - pole wspolne
}
}
CS0120 — najczęstszy komunikat tej lekcji
„Wymagane jest odwołanie do obiektu dla pola niestatycznego” znaczy: metoda statyczna próbuje sięgnąć po pole obiektu, a nie wie, o który obiekt chodzi. Rozwiązania są dwa: usunąć static z metody albo przekazać jej obiekt jako parametr.
Trzy uczciwe zastosowania i jedna pułapka
1. Stałe i wartości wspólne
public static class Fizyka
{
public const double G = 9.81;
}
Console.WriteLine(Math.PI); // tak samo dziala biblioteka .NET
2. Metody pomocnicze bez stanu
Metoda, która liczy wynik wyłącznie z argumentów, nie potrzebuje obiektu:
public static class Konwerter
{
public static double NaFarenheita(double celsjusz) => celsjusz * 9 / 5 + 32;
public static double NaCelsjusza(double farenheit) => (farenheit - 32) * 5 / 9;
}
Console.WriteLine(Konwerter.NaFarenheita(20)); // 68
Cała klasa Math działa dokładnie tak: Math.Abs, Math.Round, Math.Max — nigdzie nie tworzysz obiektu Math.
3. Liczniki i rejestry dotyczące całej klasy
Liczba utworzonych obiektów, wspólny limit, numer kolejny — to, co opisuje zbiór, a nie egzemplarz.
Klasa statyczna
public static class Konwerter
{
// wszystkie skladowe musza byc static
// nie da sie napisac new Konwerter()
}
Pułapka: stan globalny
Pole static, które zmienia się w trakcie działania programu, to wspólna zmienna dla całej aplikacji. Dwa miejsca programu mogą ją zmieniać niezależnie od siebie, a błąd objawia się w trzecim. Statyczne trzymamy stałe i metody bez stanu; statyczny zmienny stan stosujemy świadomie i rzadko — licznik biletów jest tu wyjątkiem uzasadnionym.
Konstruktor statyczny
class Konfiguracja
{
public static readonly string Wersja;
static Konfiguracja() // bez modyfikatora dostepu i bez parametrow
{
Wersja = "1.0";
Console.WriteLine("konstruktor statyczny wykonal sie raz");
}
}
Wykonuje się raz, automatycznie, przed pierwszym użyciem klasy. Nie wywołujesz go sam i nie wiesz dokładnie kiedy się uruchomi — dlatego nie umieszcza się w nim niczego, co zależy od kolejności.
Przykład: kasa biletowa z licznikiem
class Bilet
{
private static int licznik = 0;
private static readonly decimal oplata = 2.00m;
public int Numer { get; }
public string Wydarzenie { get; }
public decimal Cena { get; }
public Bilet(string wydarzenie, decimal cena)
{
licznik++;
Numer = licznik;
Wydarzenie = wydarzenie;
Cena = cena + oplata;
}
public static int LiczbaWydanych => licznik;
public static decimal Utarg { get; private set; }
public void Sprzedaj()
{
Utarg += Cena;
}
public string Opis() => $"#{Numer,-3} {Wydarzenie,-16} {Cena,6:F2} zl";
}
class Program
{
static void Main()
{
Bilet a = new Bilet("Turniej retro", 18.50m);
Bilet b = new Bilet("Noc robotow", 12.00m);
Bilet c = new Bilet("Turniej retro", 18.50m);
a.Sprzedaj();
c.Sprzedaj();
Console.WriteLine(a.Opis());
Console.WriteLine(b.Opis());
Console.WriteLine(c.Opis());
Console.WriteLine();
Console.WriteLine($"Wydanych biletow: {Bilet.LiczbaWydanych}");
Console.WriteLine($"Utarg: {Bilet.Utarg:F2} zl");
// Console.WriteLine(a.LiczbaWydanych); // CS0176
}
}
#1 Turniej retro 20,50 zl
#2 Noc robotow 14,00 zl
#3 Turniej retro 20,50 zl
Wydanych biletow: 3
Utarg: 41,00 zl
Co dzieje się po kolei
- Trzy bilety dostały numery 1, 2 i 3, bo licznik jest jeden dla całej klasy.
Numer,WydarzenieiCenaopisują konkretny bilet — to składowe obiektu.LiczbaWydanychiUtargopisują całą sprzedaż — to składowe statyczne, odczytywane przez nazwę klasy.Utargma prywatny setter, więc rośnie tylko przez metodęSprzedaj. Dwa bilety sprzedane, trzeci nie — stąd 41 zamiast 55.oplatajest statyczna ireadonly: jedna wartość dla wszystkich biletów, nie do zmiany w trakcie.- Zakomentowany wiersz pokazuje CS0176 — składowej statycznej nie odczytamy przez zmienną obiektową.
Zestawienie elementów
| Element | Znaczenie | Uwagi |
|---|---|---|
static przy polu | jedno pole na całą klasę | Istnieje niezależnie od tego, czy powstał jakikolwiek obiekt. |
static przy metodzie | metoda typu | Nie widzi pól obiektu; próba użycia daje CS0120. |
static class | klasa bez obiektów | Wszystkie składowe muszą być statyczne; new jest zabronione. |
const | stała kompilacji | Zawsze statyczna, choć nie piszemy static. Tylko typy proste i napisy. |
static readonly | stała ustalana w trakcie | Dla wartości, których nie da się policzyć przy kompilacji. |
| konstruktor statyczny | jednorazowa inicjalizacja typu | Bez modyfikatora i bez parametrów; wykonuje się raz, automatycznie. |
Klasa.Skladowa | odwołanie do składowej statycznej | Przez nazwę typu, nigdy przez zmienną obiektową. |
| CS0176 / CS0120 | komunikaty | Statyczna przez obiekt / pole obiektu w metodzie statycznej. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
Pole licznika bez static | Każdy obiekt zaczyna liczyć od nowa — dokładnie przykład z początku lekcji. |
obiekt.SkladowaStatyczna | CS0176. Odwołuj się przez nazwę klasy. |
| Pole obiektu w metodzie statycznej | CS0120 — metoda nie wie, o który obiekt chodzi. |
static „żeby się skompilowało” | Dopisanie static do wszystkiego usuwa błędy i przy okazji usuwa sens obiektów. |
| Zmienny stan statyczny w całej aplikacji | Dwa miejsca zmieniają tę samą wartość, a błąd wychodzi w trzecim. Statyczne trzymaj stałe i metody bez stanu. |
const dla wartości liczonej w trakcie | const wymaga wartości znanej przy kompilacji. Użyj static readonly. |
| Wywołanie konstruktora statycznego ręcznie | Nie da się — uruchamia go środowisko przed pierwszym użyciem klasy. |
Zadania
Napisz klasę Uczestnik, która w konstruktorze zwiększa statyczny licznik. Utwórz pięć obiektów i wypisz liczbę utworzonych przez nazwę klasy.
Rozbuduj klasę tak, żeby każdy uczestnik dostawał kolejny numer zapisany we właściwości tylko do odczytu. Wypisz numery wszystkich pięciu.
Napisz klasę statyczną Konwerter z metodami przeliczającymi temperaturę w obie strony oraz kilometry na mile. Sprawdź, jaki komunikat daje próba napisania new Konwerter().
W klasie Kolo zapisz liczbę pi na trzy sposoby: jako const, jako static readonly i jako zwykłe pole obiektu. Dla każdego wypisz w komentarzu, kiedy jest ustalana wartość i ile jest egzemplarzy.
Napisz klasę Licznik z jednym polem statycznym i jednym polem obiektu, obydwoma zwiększanymi w tej samej metodzie. Utwórz trzy obiekty, wywołaj metodę po dwa razy na każdym i wypisz oba liczniki. Wyjaśnij różnicę wyników.
Napisz klasę Sprzet, w której każdy obiekt ma własny numer inwentarzowy, a klasa pamięta łączną liczbę wypożyczeń wszystkich egzemplarzy oraz numer ostatnio wypożyczonego. Dodaj statyczną metodę zerującą rejestr i zastanów się, komu wolno ją wywołać. W komentarzu napisz, które dane musiały być statyczne i dlaczego reszta nie mogła.
Co trzeba zapamiętać
- Składowa
staticnależy do klasy; istnieje w jednym egzemplarzu niezależnie od liczby obiektów. - Odwołujemy się do niej przez nazwę typu, nigdy przez zmienną obiektową (CS0176).
- Metoda statyczna nie widzi pól obiektu (CS0120).
- Pytanie rozstrzygające: czy wartość opisuje jeden obiekt, czy wszystkie.
- Klasa statyczna nie pozwala tworzyć obiektów — tak działa
Math. constto wartość znana przy kompilacji,static readonly— ustalana w trakcie.- Zmienny stan statyczny to wspólna zmienna całej aplikacji; używaj go świadomie i rzadko.
Dokumentacja: Microsoft Learn — temat tej lekcji.