Pola — pamięć konkretnego obiektu

Robot pamięta poziom energii pomiędzy wywołaniami metod, a zmienna licząca koszt jednego ruchu znika zaraz po użyciu. To dwie różne role i dwa różne miejsca w kodzie.

C# pola readonly wartości domyślne 45–90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Odróżnisz pole od parametru i zmiennej lokalnej.
  • Wskażesz, jak długo żyje każda z nich.
  • Podasz wartość domyślną pola dla typu liczbowego, logicznego i tekstowego.
  • Ustawisz wartość początkową pola i zabezpieczysz ją słowem readonly.
  • Wyjaśnisz, dlaczego dwa obiekty tej samej klasy mają niezależne pola.

Przygotowanie: lekcje 01–37. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.

TEORIA

Trzy rodzaje zmiennych w klasie

W metodzie obiektu spotykają się trzy rodzaje zmiennych. Wyglądają podobnie, a różnią się tym, jak długo żyją.

definicja typu
class Robot
{
    private int energia = 10;                 // POLE

    public void Rusz(int odleglosc)           // PARAMETR
    {
        int koszt = odleglosc * 2;            // ZMIENNA LOKALNA

        energia -= koszt;
        Console.WriteLine($"Zuzyto {koszt}, zostalo {energia}");
    }
}
PoleParametrZmienna lokalna
Gdzie deklarowanew klasie, poza metodamiw nawiasie metodyw ciele metody
Skąd bierze wartośćz inicjalizatora albo z metodod wywołującegoz przypisania w metodzie
Jak długo istniejetak długo jak obiektjedno wywołanie metodyjedno wywołanie metody
Kto ją widziwszystkie metody obiektutylko ta metodatylko ta metoda
Po co jestpamięć obiektudane wejściowechwilowe obliczenie
Program.cs
Robot r = new Robot();

r.Rusz(3);     // Zuzyto 6, zostalo 4
r.Rusz(1);     // Zuzyto 2, zostalo 2

Zwróć uwagę na drugie wywołanie: energia pamięta wynik pierwszego. Gdyby była zmienną lokalną, za każdym razem zaczynałaby od nowa. Pole to pamięć obiektu między wywołaniami metod.

Test na pole

Zadaj sobie pytanie: „czy ta wartość ma przetrwać do następnego wywołania metody?”. Jeśli tak — to pole. Jeśli służy tylko do policzenia czegoś tu i teraz — to zmienna lokalna. Trzymanie w polu czegoś, co jest potrzebne przez trzy wiersze, tylko zaśmieca obiekt.

TEORIA

Pole zawsze ma jakąś wartość

Zmienna lokalna bez przypisania nie da się użyć — kompilator zgłosi błąd CS0165. Z polem jest inaczej: pole dostaje wartość domyślną swojego typu, nawet jeśli nic mu nie przypiszesz.

Typ polaWartość domyślna
int, long, double, decimal0
boolfalse
charznak o kodzie 0
string i inne typy referencyjnenull
Program.cs
class Robot
{
    public int Energia;          // 0
    public bool Wlaczony;        // false
    public string Nazwa;         // null !
}

Robot r = new Robot();
Console.WriteLine(r.Energia);           // 0
Console.WriteLine(r.Wlaczony);          // False
// Console.WriteLine(r.Nazwa.Length);   // NullReferenceException

Domyślny null to źródło kłopotów

Pole typu string bez wartości początkowej jest null, a nie pustym napisem. Pierwsze wywołanie jakiejkolwiek metody na nim kończy się wyjątkiem. Dlatego pola tekstowe warto od razu ustawiać: public string Nazwa = ""; — albo wymagać nazwy w konstruktorze, co zobaczysz w lekcji 40.

Inicjalizator pola

definicja typu
class Robot
{
    private int energia = 10;                          // wartosc startowa
    private string nazwa = "bez nazwy";
    private readonly DateTime utworzony = DateTime.Now;
}

Wartość z inicjalizatora jest ustawiana przy każdym new, zanim wykona się cokolwiek innego. Dzięki temu obiekt od pierwszej chwili jest w sensownym stanie.

readonly — wartość ustalana raz

Pole oznaczone readonly wolno ustawić tylko w inicjalizatorze albo w konstruktorze. Później żadna metoda go nie zmieni — próba kończy się błędem kompilacji. Używamy go do rzeczy, które dla danego obiektu są stałe: numeru seryjnego, daty utworzenia, maksymalnej pojemności.

TEORIA

Każdy obiekt ma własny komplet pól

Program.cs
Robot a = new Robot();
Robot b = new Robot();

a.Rusz(4);       // zuzywa energie robota a

Console.WriteLine(a.Energia);   // 2
Console.WriteLine(b.Energia);   // 10 - b nie ruszyl sie z miejsca

Klasa jest jedna, ale pola powstają osobno dla każdego obiektu. To właśnie dlatego dwóch graczy z poprzedniej lekcji mogło mieć różne punkty, choć opisywała ich ta sama klasa.

Wyjątek od tej zasady

Pole oznaczone static należy do klasy, a nie do obiektu — istnieje w jednym egzemplarzu, wspólnym dla wszystkich. Przydaje się na przykład do liczenia, ile obiektów utworzono. To temat lekcji 44; do tego czasu wszystkie pola piszemy bez static.

Nazewnictwo pól

ZapisKiedy stosowany
private int energia;pole prywatne — camelCase, tak piszemy w tym kursie
private int _energia;ten sam zapis z podkreśleniem; częsty w projektach zespołowych, bo od razu widać, że to pole
public int Energia;pole publiczne — PascalCase; w praktyce zastępowane właściwością z lekcji 41
PRZYKŁAD

Przykład: robot, który pamięta

Program.cs
class Robot
{
    private int energia = 10;
    private int przejechane = 0;
    private readonly string nazwa;

    public Robot(string nazwa)
    {
        this.nazwa = nazwa;          // 'this' rozroznia pole od parametru
    }

    public void Rusz(int odleglosc)
    {
        int koszt = odleglosc * 2;   // zmienna lokalna - znika po tej metodzie

        if (koszt > energia)
        {
            Console.WriteLine($"{nazwa}: za malo energii na {odleglosc} m");
            return;
        }

        energia -= koszt;
        przejechane += odleglosc;
        Console.WriteLine($"{nazwa}: przejechal {odleglosc} m, energia {energia}");
    }

    public void Naladuj()
    {
        energia = 10;
        Console.WriteLine($"{nazwa}: naladowany do {energia}");
    }

    public string Raport()
    {
        return $"{nazwa}: lacznie {przejechane} m, energia {energia}";
    }
}

class Program
{
    static void Main()
    {
        Robot alfa = new Robot("Alfa");
        Robot beta = new Robot("Beta");

        alfa.Rusz(3);
        alfa.Rusz(1);
        alfa.Rusz(5);      // za malo energii

        beta.Rusz(2);

        Console.WriteLine();
        Console.WriteLine(alfa.Raport());
        Console.WriteLine(beta.Raport());

        Console.WriteLine();
        alfa.Naladuj();
        Console.WriteLine(alfa.Raport());
    }
}
wynik w konsoli
Alfa: przejechal 3 m, energia 4
Alfa: przejechal 1 m, energia 2
Alfa: za malo energii na 5 m
Beta: przejechal 2 m, energia 6

Alfa: lacznie 4 m, energia 2
Beta: lacznie 2 m, energia 8

Alfa: naladowany do 10
Alfa: lacznie 4 m, energia 10

Co dzieje się po kolei

  • energia i przejechane to pola — pamiętają stan między wywołaniami. koszt to zmienna lokalna: powstaje i znika przy każdym wywołaniu Rusz.
  • Trzecie wywołanie alfa.Rusz(5) zostaje odrzucone, a pola nie zmieniają się ani o jednostkę. Sprawdzenie jest w środku obiektu, więc nikt nie może go pominąć.
  • Roboty są niezależne: Beta ma 8 jednostek energii, choć Alfa zjechała prawie do zera.
  • nazwa jest readonly — ustawiona raz w konstruktorze i już nie do zmiany. Żadna metoda nie może przemianować robota.
  • Po naładowaniu energia wraca do 10, ale przejechane zostaje — to dwa osobne pola o dwóch różnych rolach.
ELEMENTY WBUDOWANE

Co robi każdy użyty element

ElementZnaczenieUwagi
polezmienna zadeklarowana w klasie, poza metodamiŻyje tak długo, jak obiekt. Widzą je wszystkie metody tego obiektu.
zmienna lokalnazmienna w ciele metodyZnika po zakończeniu metody. Bez przypisania nie da się jej użyć (CS0165).
parametrzmienna w nawiasie metodyDostaje wartość przy wywołaniu; istnieje przez jedno wywołanie.
private int x = 5;inicjalizator polaUstawia wartość przy każdym new, przed wykonaniem konstruktora.
readonlypole ustalane razZapis tylko w inicjalizatorze albo konstruktorze; później błąd kompilacji.
wartość domyślna0, false, nullPole ma ją zawsze, nawet bez inicjalizatora — także niechciane null.
this.nazwa = nazwa;rozróżnienie pola i parametruPo lewej pole obiektu, po prawej parametr o tej samej nazwie.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
Pole zadeklarowane wewnątrz metodyTo zmienna lokalna — znika po zakończeniu metody, a obiekt niczego nie pamięta. Sprawdź, czy deklaracja jest poza klamrami metod.
public string Nazwa; bez wartościPole jest null; pierwsze Nazwa.Length daje NullReferenceException.
nazwa = nazwa; w konstruktorzeParametr przypisany sam do siebie — pole zostaje puste. Potrzebne jest this.nazwa = nazwa;.
Zmiana pola readonly w metodzieCS0191. Jeśli wartość ma się zmieniać, nie może być readonly.
Trzymanie wyniku pośredniego w poluObiekt „pamięta” coś, czego pamiętać nie powinien; łatwo o wynik z poprzedniego wywołania. Do obliczeń służą zmienne lokalne.
Pola publiczne w każdej klasieKażdy może je zmienić na dowolną wartość, omijając sprawdzenia. Lekcje 39 i 41 pokazują, jak to zamknąć.
ZADANIA

Zadania

ZAD 1Pole czy lokalna★☆☆

Napisz klasę Licznik z metodą Zwieksz(). Najpierw zadeklaruj licznik jako zmienną lokalną w metodzie i wywołaj metodę trzy razy. Zapisz wynik. Potem przenieś licznik do pola i powtórz. Wyjaśnij różnicę w komentarzu.

ZAD 2Wartości domyślne★☆☆

Napisz klasę z czterema polami publicznymi typów int, double, bool i string, nie nadając im wartości. Utwórz obiekt i wypisz wszystkie cztery. Zapisz, które wypisanie skończyło się wyjątkiem i dlaczego.

ZAD 3Stan konta★☆☆

Napisz klasę Konto z polem saldo ustawionym na 0 w inicjalizatorze oraz metodami Wplata i Stan. Wykonaj trzy wpłaty i pokaż, że saldo się kumuluje.

ZAD 4Numer, którego nie da się zmienić★★☆

Do klasy Konto dodaj pole readonly string numer, ustawiane w konstruktorze. Spróbuj zmienić je w metodzie i zapisz treść błędu kompilatora. Utwórz dwa konta o różnych numerach i pokaż, że ich salda są niezależne.

ZAD 5Ile pól naprawdę potrzeba★★☆

Napisz klasę Koszyk z polami przechowującymi liczbę pozycji i łączną wartość oraz metodą Dodaj(decimal cena, int sztuki). Wewnątrz metody wartość pozycji policz w zmiennej lokalnej. Uzasadnij w komentarzu, dlaczego ta wartość nie jest polem.

ZAD 6Automat z monetami★★★

Napisz klasę Automat pamiętającą stan kasy, liczbę sprzedanych napojów i cenę jednostkową ustawianą w konstruktorze jako readonly. Metoda Wrzuc(decimal kwota) ma przyjmować monety, a Kup() wydawać napój wraz z resztą, gdy wrzucona kwota wystarcza, albo odmawiać z komunikatem. Po każdej sprzedaży wrzucona kwota wraca do zera. Sprawdź program na czterech sytuacjach, w tym na zbyt małej kwocie i na dwóch zakupach pod rząd.

PODSUMOWANIE

Co trzeba zapamiętać

  • Pole żyje tak długo jak obiekt; zmienna lokalna i parametr — przez jedno wywołanie metody.
  • Pole to pamięć obiektu między wywołaniami metod.
  • Pole zawsze ma wartość: domyślnie 0, false albo null.
  • Pole typu string bez inicjalizatora jest null, a nie pustym napisem.
  • Inicjalizator pola wykonuje się przy każdym new, przed konstruktorem.
  • readonly pozwala ustawić wartość tylko w inicjalizatorze albo konstruktorze.
  • Każdy obiekt ma własny komplet pól — z wyjątkiem pól static z lekcji 44.

Dokumentacja: Microsoft Learn — temat tej lekcji.