CodeGym /Kursy /C# SELF /Automatyczne właściwości i settery tylko init-only

Automatyczne właściwości i settery tylko init-only

C# SELF
Poziom 17 , Lekcja 2
Dostępny

1. Automatyczne właściwości

W C# właściwości (property) — to coś pomiędzy polem a metodą. W zasadzie to syntactic sugar, który sprawia, że praca z enkapsulacją jest mega wygodna. Odwołujesz się do właściwości jakby to było zwykłe pole, a w środku — może być dowolna logika: sprawdzanie danych, zmiana innych pól, wywołanie metod, a nawet wysłanie e-maila twojej babci (tego ostatniego nie polecam). W poprzednim wykładzie pisaliśmy właściwości ręcznie. Ale to szybko się nudzi, jeśli masz 100500 prostych obiektów, gdzie potrzebny jest tylko "getter" i "setter" — bez żadnej logiki.

Poznaj — automatyczne właściwości. Pozwolą ci pozbyć się nudnego kodu i sprawią, że C# sam zadba o przechowywanie wartości bez twojego udziału.

Jak było kiedyś: ręczne właściwości

Kiedy chcemy ukryć pole, ale dać do niego dostęp przez właściwość, zwykle piszemy tak:


public class Dog
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}
Ręczna właściwość z prywatnym polem

Taki kod to typowa "nuda" (boilerplate). A przecież często nie trzeba nic "dodawać" do tych get/set! Po prostu dać dostęp — i tyle.

Jak można teraz

W C# wszystko stało się prostsze: już nie trzeba ręcznie pisać pól — kompilator robi to za ciebie. Wystarczy napisać:


public class Dog
{
    public string Name { get; set; }
}
Automatyczna właściwość

I gotowe. Możesz spokojnie czytać i zmieniać wartość Name, a pod spodem kompilator sam tworzy prywatne pole, do którego z zewnątrz nie da się dostać bezpośrednio.

Co się tak naprawdę dzieje za kulisami? Gdy w kodzie pojawia się public string Name { get; set; }, kompilator automatycznie tworzy ukryte pole w stylu Name__BackingField. Przy każdym odwołaniu do Name wstawia odpowiedni getter lub setter. Samo pole nie jest widoczne w kodzie, ale istnieje — i przez refleksję można je nawet znaleźć (ale to już inna bajka).

Co jest fajnego w tym podejściu? Po pierwsze, jest dużo krócej i nie trzeba powtarzać tego samego. Po drugie, kod jest czyściejszy: mniej wizualnego szumu, łatwiej czytać i utrzymywać. I wreszcie, to bezpieczniejsze — nikt nie pogrzebie bezpośrednio w polach, dostęp jest tylko przez właściwości.

2. Składnia automatycznych właściwości

Składnia jest bardzo prosta:


public class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }
}
Kilka automatycznych właściwości

Teraz nasz Dog wygląda dużo zgrabniej! Możemy tworzyć obiekty i ustawiać wartości po swojemu:

Dog myDog = new Dog();
myDog.Name = "Barbos";
myDog.Age = 4;

Schematycznie

Sposób Składnia Dostęp do wartości
Pole
public string Name;
Dostęp bezpośredni
Ręczna właściwość
public string Name { get; set; } + private string name;
Dostęp przez get/set
Auto-właściwość
public string Name { get; set; }
get/set + ukryte pole

3. Customizacja dostępu: tylko do odczytu, tylko do zapisu

Tylko do odczytu: get-only

Jeśli właściwość ma być tylko do odczytu (np. numer rejestracyjny zwierzaka), można zrobić ją tylko do czytania — podając get, a set w ogóle nie pisać albo zrobić prywatnym. Taką wartość można przypisać tylko w konstruktorze lub od razu przy deklaracji:


public class Dog
{
    public string RegistrationCode { get; }

    public Dog()
    {
        RegistrationCode = "DOG-001"; // Przypisujemy wartość w konstruktorze
    }
}

Teraz z zewnątrz można tylko odczytać RegistrationCode, ale nie zmienić. Świetny sposób, by chronić ważne dane przed przypadkową zmianą.

Jeszcze opcja: prywatny set


public string Name { get; private set; }
Prywatny setter — zmiana tylko wewnątrz klasy

Wtedy właściwość można zmienić tylko w klasie (np. przez metodę lub konstruktor).

Tylko do zapisu: kiepski pomysł

Teoretycznie można zrobić set-only właściwość (tylko do zapisu), ale to rzadko się spotyka i nie jest polecane — wyobraź sobie obiekt, który działa jak "czarna dziura": można coś tam wrzucić, ale już nigdy nie wyciągniesz.

4. init-only settery

Trochę historii

Kiedyś, żeby zrobić niezmienny obiekt (immutable), trzeba było pisać tylko get-właściwości i ustawiać wartości przez konstruktor. OK, wygodne — ale nie zawsze. Ostatnio w C# pojawiła się nowa składnia: init-only właściwości.

O co chodzi z init-setterem?

  • Pozwala ustawić wartość właściwości tylko przy tworzeniu obiektu (czyli w konstruktorze albo w inicjalizatorze obiektu).
  • Po utworzeniu obiektu właściwość jest tylko do odczytu.
  • Świetnie nadaje się do "prawie niezmiennych" klas, DTO, konfiguracji, modeli.

public class Dog
{
    public string Name { get; init; }
    public int Age { get; init; }
}
Właściwości z init-setterami

Teraz możemy zrobić tak:

Dog dog = new Dog { Name = "Bobik", Age = 2 };
dog.Name = "Rex"; // Błąd! Po utworzeniu - tylko do odczytu

W konstruktorze też można ustawiać wartości:

public Dog(string name, int age)
{
    Name = name;
    Age = age;
}

Schemat: kiedy można przypisywać

Gdzie przypisywać? { get; set; } { get; private set; } { get; init; }
Poza klasą, po utworzeniu
W konstruktorze
W inicjalizatorze
Wewnątrz klasy ❌ (oprócz konstruktora)

5. Praktyczne zastosowanie

Przepiszmy naszą klasę Dog, używając automatycznych i init-only właściwości:


public class Dog
{
    // Właściwość, ustawiana tylko przy inicjalizacji
    public string Name { get; init; }
    public int Age { get; init; }

    // Automatyczna właściwość dla aktualnego stanu
    public bool IsHungry { get; set; }

    // Metoda, akcje psa
    public void Bark()
    {
        Console.WriteLine($"{Name} mówi: Hau!");
    }
}

Teraz możemy tworzyć Dog tak:

Dog dog = new Dog { Name = "Szaryk", Age = 3, IsHungry = true };

dog.Bark(); // wypisze: Szaryk mówi: Hau!
dog.IsHungry = false; // to właściwość można zmieniać po utworzeniu
dog.Name = "Barbos"; // Błąd! Właściwość tylko do inicjalizacji

6. Automatyczne właściwości z wartościami domyślnymi

W C# wszystko jest dla twojej wygody. Możesz nawet od razu ustawić wartość domyślną właściwości:


public class Dog
{
    public string Name { get; set; } = "Bez imienia";
    public int Age { get; set; } = 0;
}
Automatyczne właściwości z wartościami domyślnymi

Albo z init:


public class Dog
{
    public string Name { get; init; } = "Szczeniak";
    public int Age { get; init; } = 0;
}
init-only właściwości z wartościami domyślnymi

Teraz jeśli nie podasz Name przy tworzeniu, będzie "Szczeniak".

7. Automatyczne właściwości z różnym poziomem dostępu

Możesz zrobić tak, żeby właściwość była czytana i zapisywana z różnymi modyfikatorami dostępu:


public class Dog
{
    public string Name { get; private set; }
    public int Age { get; private set; }
    
    public Dog(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

W tym przypadku zmienić imię i wiek można tylko w klasie Dog, np. w metodach lub konstruktorze.

Porównanie ręcznych i automatycznych właściwości

Metoda Trudność pisania Kontrola w set/get Zmiana po utworzeniu Zastosowanie w modelach
Ręczna właściwość Długo Pełna Dowolna Gdy potrzebna logika
Automatyczna właściwość Krótko Brak Dowolna lub prywatna Prawie wszędzie
init-only automatyczna Krótko Brak Tylko przy tworzeniu DTO, konfiguracje

8. Typowe błędy przy pracy z właściwościami

Błąd nr 1: próba przypisania wartości do get-only właściwości poza konstruktorem.
Jeśli właściwość jest tylko z get, kompilator nie pozwoli zmienić jej wartości gdzie indziej. Przypisywać można tylko w konstruktorze lub od razu przy deklaracji. Zapomnisz — dostaniesz błąd kompilacji.

Błąd nr 2: pomylenie pola z właściwością.
Gdy zamieniasz zwykłe pole na automatyczną właściwość, upewnij się, że w reszcie kodu nie ma bezpośredniego odwołania do pola. Pole i właściwość to różne rzeczy, i jeśli zostawisz stare odwołania, kompilator wywali błąd albo program będzie się dziwnie zachowywał.

Błąd nr 3: prywatny set bez zrozumienia skutków.
Jeśli właściwość ma prywatny set, a w reszcie kodu próbujesz jej przypisać wartość — będzie błąd. Często dzieje się to przypadkiem, zwłaszcza przy kopiowaniu cudzego kodu. Zawsze sprawdzaj, czy set jest dostępny tam, gdzie chcesz go użyć.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION