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.

C# static const klasa statyczna 45–90 min
CEL LEKCJI

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 const od static 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.

TEORIA

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.

tak nie zadziała
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:

Program.cs
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.

TEORIA

Odwołanie przez nazwę typu

Program.cs
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 obiektuSkładowa static
Ile egzemplarzypo jednym na obiektjeden na całą klasę
Odwołanieobiekt.NazwaKlasa.Nazwa
Widzi pola obiektutaknie — nie wie, o który obiekt chodzi
Widzi pola statycznetaktak
Powstajeprzy newprzy pierwszym użyciu klasy
definicja typu
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.

TEORIA

Trzy uczciwe zastosowania i jedna pułapka

1. Stałe i wartości wspólne

Program.cs
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:

Program.cs
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

Program.cs
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

definicja typu
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

Przykład: kasa biletowa z licznikiem

Program.cs
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
    }
}
wynik w konsoli
#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, Wydarzenie i Cena opisują konkretny bilet — to składowe obiektu.
  • LiczbaWydanych i Utarg opisują całą sprzedaż — to składowe statyczne, odczytywane przez nazwę klasy.
  • Utarg ma prywatny setter, więc rośnie tylko przez metodę Sprzedaj. Dwa bilety sprzedane, trzeci nie — stąd 41 zamiast 55.
  • oplata jest statyczna i readonly: jedna wartość dla wszystkich biletów, nie do zmiany w trakcie.
  • Zakomentowany wiersz pokazuje CS0176 — składowej statycznej nie odczytamy przez zmienną obiektową.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
static przy polujedno pole na całą klasęIstnieje niezależnie od tego, czy powstał jakikolwiek obiekt.
static przy metodziemetoda typuNie widzi pól obiektu; próba użycia daje CS0120.
static classklasa bez obiektówWszystkie składowe muszą być statyczne; new jest zabronione.
conststała kompilacjiZawsze statyczna, choć nie piszemy static. Tylko typy proste i napisy.
static readonlystała ustalana w trakcieDla wartości, których nie da się policzyć przy kompilacji.
konstruktor statycznyjednorazowa inicjalizacja typuBez modyfikatora i bez parametrów; wykonuje się raz, automatycznie.
Klasa.Skladowaodwołanie do składowej statycznejPrzez nazwę typu, nigdy przez zmienną obiektową.
CS0176 / CS0120komunikatyStatyczna przez obiekt / pole obiektu w metodzie statycznej.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
Pole licznika bez staticKażdy obiekt zaczyna liczyć od nowa — dokładnie przykład z początku lekcji.
obiekt.SkladowaStatycznaCS0176. Odwołuj się przez nazwę klasy.
Pole obiektu w metodzie statycznejCS0120 — 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 aplikacjiDwa 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 trakcieconst wymaga wartości znanej przy kompilacji. Użyj static readonly.
Wywołanie konstruktora statycznego ręcznieNie da się — uruchamia go środowisko przed pierwszym użyciem klasy.
ZADANIA

Zadania

ZAD 1Licznik obiektów★☆☆

Napisz klasę Uczestnik, która w konstruktorze zwiększa statyczny licznik. Utwórz pięć obiektów i wypisz liczbę utworzonych przez nazwę klasy.

ZAD 2Numer kolejny★☆☆

Rozbuduj klasę tak, żeby każdy uczestnik dostawał kolejny numer zapisany we właściwości tylko do odczytu. Wypisz numery wszystkich pięciu.

ZAD 3Klasa narzędziowa★★☆

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().

ZAD 4Stała czy pole★★☆

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.

ZAD 5Statyczna kontra obiektowa★★☆

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.

ZAD 6Rejestr wypożyczeń★★★

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.

PODSUMOWANIE

Co trzeba zapamiętać

  • Składowa static należ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.
  • const to 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.