Właściwości — wygodny odczyt, kontrolowany zapis

Pole publiczne jest wygodne, ale przyjmie minus czterysta stopni. Para metod pilnuje zakresu, lecz użycie robi się rozwlekłe. Właściwość daje jedno i drugie.

C# get i set value właściwości wyliczane 45–90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Zamienisz pole publiczne na właściwość automatyczną.
  • Dodasz do zapisu kontrolę wartości i użyjesz słowa value.
  • Ograniczysz zapis do wnętrza klasy przez private set.
  • Napiszesz właściwość wyliczaną w zapisie ze strzałką.
  • Rozstrzygniesz, kiedy lepsza jest właściwość, a kiedy metoda.

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

TEORIA

Pole jest wygodne, metoda bezpieczna

Termostat ma przechowywać zadaną temperaturę. Pole publiczne jest wygodne w użyciu, ale nie pilnuje niczego:

wersja bez kontroli
class Termostat
{
    public int Temperatura;
}

Termostat t = new Termostat();
t.Temperatura = 22;      // wygodnie
t.Temperatura = -400;    // ...i nic tego nie powstrzyma

Para metod z lekcji 39 pilnuje zakresu, ale użycie robi się rozwlekłe:

wersja bezpieczna, ale niewygodna
class Termostat
{
    private int temperatura = 20;

    public int Odczytaj() { return temperatura; }

    public void Ustaw(int wartosc)
    {
        if (wartosc < 5 || wartosc > 30) return;
        temperatura = wartosc;
    }
}

t.Ustaw(22);
Console.WriteLine(t.Odczytaj());

Właściwość łączy oba podejścia: w użyciu wygląda jak pole, a w środku wykonuje kod.

wersja z właściwością
class Termostat
{
    private int temperatura = 20;

    public int Temperatura
    {
        get { return temperatura; }
        set
        {
            if (value < 5 || value > 30) return;
            temperatura = value;
        }
    }
}

Termostat t = new Termostat();
t.Temperatura = 22;                     // wyglada jak pole
Console.WriteLine(t.Temperatura);       // 22
t.Temperatura = -400;                   // odrzucone przez set
Console.WriteLine(t.Temperatura);       // 22 - bez zmian

Słowo value

W bloku set zmienna value pojawia się sama — to wartość stojąca po prawej stronie znaku równości w miejscu użycia. Nie deklarujemy jej i nie wolno nazwać inaczej.

TEORIA

Cztery zapisy, które trzeba znać

1. Właściwość automatyczna

Gdy nie ma czego sprawdzać, kompilator utworzy pole za ciebie:

wewnątrz klasy
public string Imie { get; set; } = "";

Pole istnieje, ale jest ukryte i nie ma nazwy, do której dałoby się sięgnąć.

2. Tylko do odczytu z zewnątrz

wewnątrz klasy
public int Punkty { get; private set; }

public void Dodaj(int ile)
{
    if (ile > 0) Punkty += ile;      // zapis z wnetrza klasy - wolno
}

Odczyt dla wszystkich, zapis tylko dla metod tej klasy. Najczęstszy zapis w całym kursie.

3. Ustawiana raz, przy tworzeniu

wewnątrz klasy
public string Numer { get; }

public Bilet(string numer)
{
    Numer = numer;      // wolno tylko tutaj albo w inicjalizatorze
}

4. Wyliczana — sam get

wewnątrz klasy
private decimal cena;
private int sztuki;

public decimal Razem
{
    get { return cena * sztuki; }
}

// zapis skrocony, dokladnie to samo:
public decimal RazemKrocej => cena * sztuki;

Taka właściwość niczego nie przechowuje. Liczy wynik przy każdym odczycie — dlatego zawsze jest aktualna.

ZapisOdczytZapis wartościKiedy
{ get; set; }wszędziewszędzieprosta dana bez reguł
{ get; private set; }wszędzietylko w klasiewartość zmieniana wyłącznie metodami
{ get; }wszędzietylko konstruktorwartość ustalana raz
{ get { … } set { … } }wszędzieprzez kod sprawdzającygdy trzeba pilnować zakresu
=> wyrażeniewszędziebrakwartość wyliczana z innych
TEORIA

Właściwość czy metoda

Skoro właściwość może wykonywać kod, gdzie przebiega granica między nią a metodą? Reguła jest prosta: właściwość opisuje cechę, metoda opisuje czynność.

ZapisDobry wybór?Dlaczego
gracz.Punktywłaściwośćto cecha gracza, odczyt jest natychmiastowy
gracz.Opiswłaściwośćkrótkie sklejenie dwóch pól
gracz.Dodaj(5)metodaczynność, która coś zmienia
plik.WczytajWszystkometodatrwa długo i może się nie udać
lista.Countwłaściwośćgotowa liczba, nic nie liczy od nowa
tekst.ToUpper()metodatworzy nowy obiekt

Właściwość nie powinna zaskakiwać

Odczyt obiekt.Cecha wygląda niewinnie, więc nie może kosztować sekundy ani zmieniać stanu obiektu. Jeśli operacja czyta plik, łączy się z siecią albo może zgłosić wyjątek — to metoda, nawet jeśli składniowo dałoby się zapisać ją jako właściwość.

Pułapka nieskończonej rekurencji

Zapis get { return Temperatura; } — z wielkiej litery, czyli odwołanie do samej siebie — tworzy nieskończoną pętlę i kończy się StackOverflowException. Właściwość ma zwracać pole, czyli temperatura z małej.

PRZYKŁAD

Przykład: termostat i bilet

Program.cs
class Termostat
{
    private int temperatura = 20;

    public string Nazwa { get; }                       // ustawiana raz
    public int LiczbaZmian { get; private set; }       // zapis tylko z wnetrza

    public Termostat(string nazwa)
    {
        Nazwa = nazwa;
    }

    public int Temperatura                             // z kontrola zakresu
    {
        get { return temperatura; }
        set
        {
            if (value < 5 || value > 30)
            {
                Console.WriteLine($"{Nazwa}: odrzucono {value} stopni");
                return;
            }

            temperatura = value;
            LiczbaZmian++;
        }
    }

    public bool Chlodno => temperatura < 18;           // wyliczana

    public string Opis => $"{Nazwa}: {temperatura} st. ({LiczbaZmian} zmian)";
}

class Program
{
    static void Main()
    {
        Termostat salon = new Termostat("Salon");

        salon.Temperatura = 22;
        salon.Temperatura = 45;        // odrzucone
        salon.Temperatura = 16;

        Console.WriteLine(salon.Opis);
        Console.WriteLine($"Chlodno: {salon.Chlodno}");

        // salon.Nazwa = "Kuchnia";        // CS0200 - brak settera
        // salon.LiczbaZmian = 100;        // CS0272 - setter jest prywatny
    }
}
wynik w konsoli
Salon: odrzucono 45 stopni
Salon: 16 st. (2 zmian)
Chlodno: True

Co dzieje się po kolei

  • Trzy przypisania, ale LiczbaZmian wynosi 2 — odrzucona wartość nie zwiększyła licznika, bo set wychodzi przed jego zmianą.
  • Chlodno nie jest przechowywane. Po ustawieniu 16 stopni odpowiedź zmienia się sama, bo liczy się przy każdym odczycie.
  • Opis korzysta z Nazwa i LiczbaZmian — właściwość może używać innych właściwości.
  • Dwa zakomentowane wiersze pokazują dwa różne komunikaty: CS0200 przy braku settera i CS0272, gdy setter istnieje, ale jest prywatny.
ELEMENTY WBUDOWANE

Co robi każdy użyty element

ElementZnaczenieUwagi
getakcesor odczytuMusi zwrócić wartość typu właściwości.
setakcesor zapisuNowa wartość jest dostępna pod nazwą value.
valuewartość przypisywanaIstnieje tylko w bloku set; nazwy nie wolno zmienić.
{ get; set; }właściwość automatycznaKompilator tworzy ukryte pole.
{ get; private set; }zapis tylko w klasieNajczęstszy wybór, gdy wartość zmieniają metody.
{ get; }tylko do odczytuUstawiana w konstruktorze albo w inicjalizatorze.
=> wyrażeniewłaściwość wyliczanaSkrót zapisu get { return …; }.
initzapis tylko przy tworzeniuPozwala użyć inicjalizatora obiektu, a potem blokuje zmiany.
CS0200 / CS0272komunikatyBrak settera / setter niedostępny w tym miejscu.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
get { return Temperatura; }Odwołanie do samej siebie — nieskończona rekurencja i StackOverflowException. Zwracaj pole z małej litery.
set { temperatura = temperatura; }Przypisanie pola do siebie. Nowa wartość kryje się pod nazwą value.
public int Punkty { get; set; } plus metoda sprawdzającaReguła da się ominąć przypisaniem. Setter musi być private.
Publiczne pole obok właściwości o tej samej roliDwa wejścia do tej samej danej; jedno z nich na pewno ominie sprawdzenia.
Właściwość czytająca plikOdczyt cechy nie powinien trwać ani zawodzić. To zadanie dla metody.
{ get; } ustawiane w metodzieCS0200 — wolno tylko w konstruktorze albo w inicjalizatorze pola.
ZADANIA

Zadania

ZAD 1Od pola do właściwości★☆☆

Weź klasę Ksiazka z publicznymi polami Tytul i Rok. Zamień je na właściwości automatyczne i sprawdź, że program działa tak samo. Zapisz w komentarzu, co w kodzie użytkownika klasy musiałeś zmienić.

ZAD 2Zapis tylko dla klasy★☆☆

Napisz klasę Licznik z właściwością Wartosc mającą publiczny get i prywatny set oraz metodami Zwieksz i Zeruj. Spróbuj przypisać wartość z Main i zapisz numer błędu.

ZAD 3Kontrola zakresu★★☆

Napisz klasę Ocena z właściwością przyjmującą wyłącznie liczby od 1 do 6. Wartość spoza zakresu ma być odrzucona z komunikatem, a poprzednia zachowana. Sprawdź dla 0, 3, 6 i 7.

ZAD 4Właściwość wyliczana★★☆

Do klasy Prostokat z właściwościami Szerokosc i Wysokosc dodaj wyliczane Pole i Obwod w zapisie ze strzałką. Pokaż, że po zmianie boku oba wyniki zmieniają się same.

ZAD 5Numer nie do zmiany★★☆

Napisz klasę Bilet z właściwością Numer ustawianą wyłącznie w konstruktorze oraz właściwością Cena z kontrolą wartości ujemnych. Spróbuj zmienić numer po utworzeniu obiektu i zapisz komunikat kompilatora.

ZAD 6Konto z historią★★★

Napisz klasę Konto, w której Saldo ma publiczny odczyt i prywatny zapis, a operacje wykonują metody Wplata i Wyplata. Dodaj właściwości wyliczane: CzyNaMinusie oraz LiczbaOperacji. Zadbaj o to, żeby żadna droga z zewnątrz nie pozwalała ustawić salda wprost, i uzasadnij w komentarzu, dlaczego LiczbaOperacji nie ma settera.

PODSUMOWANIE

Co trzeba zapamiętać

  • Właściwość wygląda w użyciu jak pole, a w środku wykonuje kod.
  • W bloku set nowa wartość kryje się pod nazwą value.
  • { get; private set; } pozwala odczytać wszystkim, a zmienić tylko metodom klasy.
  • { get; } ustawia się w konstruktorze i już nigdy więcej.
  • Właściwość wyliczana nie przechowuje nic — liczy przy każdym odczycie.
  • Właściwość opisuje cechę; jeśli operacja trwa, zawodzi albo zmienia stan, to metoda.

Dokumentacja: Microsoft Learn — temat tej lekcji.