Właściwości (Properties) w C#

1. Wprowadzenie – Czym są właściwości?

Właściwości to członkowie klasy, które pozwalają nam kontrolować dostęp do danych. Wyobraź sobie, że masz dom z drzwiami wejściowymi. Ty decydujesz, kto może wejść (dostęp do pisania), kto może wyjść (dostęp do czytania), a możesz też postawić ochroniarza, który sprawdzi, czy osoba ma uprawnienia, zanim wpuści ją do domu.

Właściwości działają dokładnie tak samo – są „strażnikami” dostępu do danych prywatnych.

2. Problem przed właściwościami – Bezpośredni dostęp do pól

Zanim nauczymy się właściwości, zobaczmy, dlaczego są potrzebne.

Zły sposób – Pole publiczne:

public class Uczeń
{
    public int Wiek; // Pole publiczne - BAD! 😟
}

// W programie:
var uczeń = new Uczeń();
uczeń.Wiek = -5; // Nikt nie zabrania! Wiek ujemny?
uczeń.Wiek = 150; // Lub 150 lat? To bez sensu!

Problem: Każdy może przypisać jakąkolwiek wartość, nawet głupią.

3. Rozwiązanie – Metody gettera i settera

Stare podejście (przed właściwościami):

public class Uczeń
{
    private int wiek; // Pole prywatne
    
    // Getter (czytanie)
    public int GetWiek()
    {
        return wiek;
    }
    
    // Setter (pisanie)
    public void SetWiek(int nowyWiek)
    {
        if (nowyWiek > 0 && nowyWiek < 120)
        {
            wiek = nowyWiek;
        }
        else
        {
            Console.WriteLine("Wiek musi być między 1 a 119!");
        }
    }
}

// Użycie:
var uczeń = new Uczeń();
uczeń.SetWiek(16); // OK ✓
uczeń.SetWiek(-5); // Komunikat: "Wiek musi być między 1 a 119!" ✗

Zaleta: Kontrolujemy, co przypisujemy! Wada: Kod jest długi i czasami mamy wrażenie, że coś wyglądałoby ładniej.

4. Nowoczesne rozwiązanie – Właściwości (Properties)

C# daje nam syntaktyczny cukier – właściwości, które działają jak pola, ale wewnątrz mają getter i setter:

Właściwość z pełną kontrolą:

public class Uczeń
{
    private int _wiek; // Pole prywatne (z podkreśleniem)
    
    // Właściwość publiczna
    public int Wiek
    {
        get
        {
            return _wiek;
        }
        set
        {
            if (value > 0 && value < 120)
            {
                _wiek = value;
            }
            else
            {
                Console.WriteLine("Wiek musi być między 1 a 119!");
            }
        }
    }
}

// Użycie - wyglądało jak zwyczajne pole!
var uczeń = new Uczeń();
uczeń.Wiek = 16;           // Setter się uruchamia
int wiekUcznia = uczeń.Wiek; // Getter się uruchamia
Console.WriteLine(wiekUcznia); // Wypisuje: 16

Magię działa! Przypisujemy i czytamy jak zwyczajne pole, ale wewnątrz to metody!

5. Krótka forma – Property Auto-Implemented

Gdy nie potrzebujemy żadnej logiki (żadnych walidacji), możemy pisać krócej:

public class Uczeń
{
    // Krótka forma - kompilator sam tworzy pole prywatne!
    public string Imię { get; set; }
    public string Nazwisko { get; set; }
    public int Wiek { get; set; }
}

// Użycie:
var uczeń = new Uczeń();
uczeń.Imię = "Jan";
uczeń.Nazwisko = "Kowalski";
uczeń.Wiek = 17;

Console.WriteLine($"{uczeń.Imię} {uczeń.Nazwisko}, wiek: {uczeń.Wiek}");
// Wypisuje: Jan Kowalski, wiek: 17

Uwaga: Kompilator automatycznie tworzy prywatne pole w tle. My tego nie widzimy, ale ono tam jest!

6. Różne rodzaje właściwości

1. Tylko do czytania (read-only)

public class Uczeń
{
    private string _numer;
    
    public string Numer
    {
        get { return _numer; }
        // Brak settera - nie można zmienić!
    }
    
    public Uczeń(string numer)
    {
        _numer = numer; // Tylko w konstruktorze możemy ustawić
    }
}

// Użycie:
var uczeń = new Uczeń("2024/001");
Console.WriteLine(uczeń.Numer); // OK: 2024/001
// uczeń.Numer = "2024/002"; // BŁĄD! Nie można przypisać

Tylko do pisania (write-only)

public class KontoBankowe
{
    private string _pin;
    
    public string PIN
    {
        set { _pin = value; }
        // Brak gettera - nie możemy czytać!
    }
}

// Użycie:
var konto = new KontoBankowe();
konto.PIN = "1234"; // OK
// string pin = konto.PIN; // BŁĄD! Nie można czytać

Z różnymi poziomami dostępu

public class Produkt
{
    private decimal _cena;
    
    public decimal Cena
    {
        get { return _cena; }
        private set { _cena = value; } // Setter tylko dla klasy
    }
    
    // Metoda wewnątrz klasy
    public void ZmienCenę(decimal nowaCena)
    {
        if (nowaCena > 0)
        {
            Cena = nowaCena; // OK - wewnątrz klasy
        }
    }
}

// Użycie:
var produkt = new Produkt();
// produkt.Cena = 100; // BŁĄD! Setter jest prywatny
produkt.ZmienCenę(100); // OK ✓

7. Praktyczne przykłady – Właściwości z logiką

Przykład 1: Koło (właściwość obliczana)

public class Koło
{
    private double _promień;
    
    public double Promień
    {
        get { return _promień; }
        set
        {
            if (value > 0)
            {
                _promień = value;
            }
        }
    }
    
    // Właściwość, która się oblicza (nie ma settera!)
    public double Obwód
    {
        get { return 2 * Math.PI * _promień; }
    }
    
    public double Pole
    {
        get { return Math.PI * _promień * _promień; }
    }
}

// Użycie:
var koło = new Koło();
koło.Promień = 5;
Console.WriteLine($"Obwód: {koło.Obwód:F2}");      // Obwód: 31,42
Console.WriteLine($"Pole: {koło.Pole:F2}");        // Pole: 78,54

Przykład 2: Konto bankowe

public class Konto
{
    private decimal _saldo;
    
    public string Właściciel { get; set; }
    
    public decimal Saldo
    {
        get { return _saldo; }
        private set { _saldo = value; }
    }
    
    public void Wpłata(decimal kwota)
    {
        if (kwota > 0)
        {
            Saldo += kwota;
            Console.WriteLine($"Wpłacono {kwota} zł. Nowe saldo: {Saldo} zł");
        }
        else
        {
            Console.WriteLine("Kwota musi być dodatnia!");
        }
    }
    
    public void Wypłata(decimal kwota)
    {
        if (kwota > 0 && kwota <= Saldo)
        {
            Saldo -= kwota;
            Console.WriteLine($"Wypłacono {kwota} zł. Nowe saldo: {Saldo} zł");
        }
        else if (kwota > Saldo)
        {
            Console.WriteLine("Niewystarczające środki!");
        }
    }
}

// Użycie:
var konto = new Konto { Właściciel = "Jan Kowalski" };
konto.Wpłata(1000);    // Wpłacono 1000 zł. Nowe saldo: 1000 zł
konto.Wypłata(300);    // Wypłacono 300 zł. Nowe saldo: 700 zł
konto.Wypłata(1000);   // Niewystarczające środki!
Console.WriteLine($"Saldo {konto.Właściciel}: {konto.Saldo} zł");

Przykład 3: Studenci i oceny

public class Estudiante
{
    private List<int> _oceny = new List<int>();
    
    public string Imię { get; set; }
    
    public List<int> Oceny
    {
        get { return _oceny; }
    }
    
    public void DodajOcenę(int ocena)
    {
        if (ocena >= 1 && ocena <= 6)
        {
            _oceny.Add(ocena);
            Console.WriteLine($"Dodano ocenę: {ocena}");
        }
        else
        {
            Console.WriteLine("Ocena musi być między 1 a 6!");
        }
    }
    
    // Właściwość tylko do czytania - oblicza średnią
    public double ŚredniaOcen
    {
        get
        {
            if (_oceny.Count == 0) return 0;
            return _oceny.Average();
        }
    }
}

// Użycie:
var student = new Estudiante { Imię = "Marta" };
student.DodajOcenę(5);
student.DodajOcenę(4);
student.DodajOcenę(5);
student.DodajOcenę(3);

Console.WriteLine($"{student.Imię}, średnia: {student.ŚredniaOcen:F2}");
// Marta, średnia: 4,25

8. Właściwości zainicjalizowane (C# 6.0+)

public class Osoba
{
    // Właściwość z wartością domyślną
    public string Imię { get; set; } = "Nieznane";
    public string Nazwisko { get; set; } = "Nieznane";
    public DateTime DataUrodzenia { get; set; } = DateTime.Now;
    
    // Tylko do czytania, ale z wartością domyślną
    public string PełneImię
    {
        get { return $"{Imię} {Nazwisko}"; }
    }
}

// Użycie:
var osoba = new Osoba(); // Wszystkie wartości domyślne
Console.WriteLine(osoba.PełneImię); // Nieznane Nieznane

osoba.Imię = "Paweł";
osoba.Nazwisko = "Nowak";
Console.WriteLine(osoba.PełneImię); // Paweł Nowak

9. Podsumowanie – Kiedy używać właściwości?

SituationCo robić?
Proste pole bez walidacjiAuto-property: public int Wiek { get; set; }
Pole z walidacjąProperty z logiką w setterze
Dane tylko do czytaniaProperty z samym getterem
Obliczane wartościProperty z logiką w getterze
Kontrola dostępuPrivate/protected settery

10. Ćwiczenia dla uczniów

Ćwiczenie 1: Klasa Samochód

Stwórz klasę Samochód z właściwościami:

  • Marka (string, auto-property)
  • Prędkość (int, z walidacją: maks 300)
  • Paliwo (decimal, getter tylko – zmienia się w metodzie Jedź)
  • Metoda Jedź(int km) – zmniejsza paliwo

Ćwiczenie 2: Klasa Gracz

Stwórz klasę Gracz z właściwościami:

  • Nazwa (auto-property)
  • Punkty (int, getter publiczny, setter prywatny)
  • Metoda ZdobądźPunkty(int punkty) – zwiększa punkty
  • Właściwość Poziom (obliczana: Punkty / 100)

Ćwiczenie 3: Klasa Temperatura

Stwórz klasę Temperatura:

  • Przechowuje temperaturę w Celsjuszach
  • Właściwość Celsius (int z walidacją)
  • Właściwość Fahrenheit (obliczana, tylko do czytania)