Właściwości w C# – get i set
Nauczysz się kontrolować dostęp do danych w klasach za pomocą właściwości. Poznasz auto-properties, walidację i właściwości obliczane.
Czym są właściwości?
Właściwości to członkowie klasy, które pozwalają nam kontrolować dostęp do danych. Są „strażnikami” dostępu do pól prywatnych.
Wyobraź sobie, że masz dom z drzwiami wejściowymi. Ty decydujesz:
- Kto może wejść – dostęp do pisania (set)
- Kto może wyjść – dostęp do czytania (get)
- Ochroniarz sprawdza uprawnienia – walidacja danych
Właściwości to kluczowy element enkapsulacji – ukrywania wewnętrznych danych klasy i udostępniania tylko kontrolowanego interfejsu.
Problem z polami publicznymi
Zanim poznamy właściwości, zobaczmy dlaczego są potrzebne.
❌ Zły sposób – pole publiczne
public class Uczen { public int Wiek; // Pole publiczne - BAD! 😟 } // W programie: var uczen = new Uczen(); uczen.Wiek = -5; // Nikt nie zabrania! Wiek ujemny? 🤔 uczen.Wiek = 150; // Lub 150 lat? To bez sensu!
Każdy może przypisać jakąkolwiek wartość, nawet całkowicie bezsensowną. Brak kontroli = błędy w programie!
Stare podejście – metody Get/Set
Przed właściwościami programiści pisali osobne metody do czytania i pisania danych.
public class Uczen { 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 uczen = new Uczen(); uczen.SetWiek(16); // OK ✓ uczen.SetWiek(-5); // "Wiek musi być między 1 a 119!" ✗
✅ Zaleta
Kontrolujemy, co przypisujemy! Walidacja działa.
❌ Wada
Kod jest długi i wygląda mniej naturalnie niż zwykłe pole.
Nowoczesne rozwiązanie – Właściwości
C# daje nam syntaktyczny cukier – właściwości, które działają jak pola, ale wewnątrz mają getter i setter.
public class Uczen { 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 jak zwyczajne pole! var uczen = new Uczen(); uczen.Wiek = 16; // Setter się uruchamia int wiekUcznia = uczen.Wiek; // Getter się uruchamia Console.WriteLine(wiekUcznia); // Wypisuje: 16
W setterze value to specjalna zmienna, która zawiera wartość przypisywaną do właściwości. Gdy piszesz uczen.Wiek = 16, to value będzie równe 16.
Przypisujemy i czytamy jak zwyczajne pole, ale wewnątrz wykonują się metody z walidacją!
Auto-properties (krótka forma)
Gdy nie potrzebujemy żadnej logiki (walidacji), możemy pisać znacznie krócej.
public class Uczen { // Krótka forma – kompilator sam tworzy pole prywatne! public string Imie { get; set; } public string Nazwisko { get; set; } public int Wiek { get; set; } } // Użycie: var uczen = new Uczen(); uczen.Imie = "Jan"; uczen.Nazwisko = "Kowalski"; uczen.Wiek = 17; Console.WriteLine($"{uczen.Imie} {uczen.Nazwisko}, wiek: {uczen.Wiek}"); // Wypisuje: Jan Kowalski, wiek: 17
Kompilator automatycznie tworzy prywatne pole w tle. My tego nie widzimy, ale ono tam jest! To tak jakbyś napisał pełną wersję z get/set.
Pełna forma (długa)
private int _wiek;
public int Wiek { get { return _wiek; } set { _wiek = value; } }
Auto-property (krótka)
public int Wiek { get; set; }
Jedna linia zamiast czterech!
Różne rodzaje właściwości
1. Tylko do czytania (read-only)
Właściwość bez settera – można tylko odczytać, nie można zmienić z zewnątrz.
public class Uczen { private string _numer; public string Numer { get { return _numer; } // Brak settera – nie można zmienić! } public Uczen(string numer) { _numer = numer; // Tylko w konstruktorze możemy ustawić } } // Użycie: var uczen = new Uczen("2024/001"); Console.WriteLine(uczen.Numer); // OK: 2024/001 // uczen.Numer = "2024/002"; // BŁĄD! Nie można przypisać
2. Z różnymi poziomami dostępu
Getter publiczny, ale setter prywatny – z zewnątrz można tylko czytać.
public class Produkt { private decimal _cena; public decimal Cena { get { return _cena; } private set { _cena = value; } // Setter tylko dla klasy } // Metoda wewnątrz klasy może używać private set public void ZmienCene(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.ZmienCene(100); // OK ✓
Gdy chcesz, żeby wartość mogła się zmieniać, ale tylko przez kontrolowane metody klasy (np. Wpłata(), Wypłata()), a nie przez bezpośrednie przypisanie.
Właściwości obliczane
Właściwość nie musi przechowywać danych – może je obliczać na podstawie innych pól!
Przykład 1: Koło
public class Kolo { private double _promien; public double Promien { get { return _promien; } set { if (value > 0) { _promien = value; } } } // Właściwości OBLICZANE – nie mają settera! public double Obwod { get { return 2 * Math.PI * _promien; } } public double Pole { get { return Math.PI * _promien * _promien; } } } // Użycie: var kolo = new Kolo(); kolo.Promien = 5; Console.WriteLine($"Obwód: {kolo.Obwod:F2}"); // Obwód: 31,42 Console.WriteLine($"Pole: {kolo.Pole:F2}"); // Pole: 78,54
Przykład 2: Konto bankowe
public class Konto { private decimal _saldo; public string Wlasciciel { get; set; } public decimal Saldo { get { return _saldo; } private set { _saldo = value; } } public void Wplata(decimal kwota) { if (kwota > 0) { Saldo += kwota; Console.WriteLine($"Wpłacono {kwota} zł. Nowe saldo: {Saldo} zł"); } } public void Wyplata(decimal kwota) { if (kwota > 0 && kwota <= Saldo) { Saldo -= kwota; Console.WriteLine($"Wypłacono {kwota} zł. Nowe saldo: {Saldo} zł"); } else { Console.WriteLine("Niewystarczające środki!"); } } } // Użycie: var konto = new Konto { Wlasciciel = "Jan Kowalski" }; konto.Wplata(1000); // Wpłacono 1000 zł. Nowe saldo: 1000 zł konto.Wyplata(300); // Wypłacono 300 zł. Nowe saldo: 700 zł konto.Wyplata(1000); // Niewystarczające środki!
Przykład 3: Student z ocenami
public class Student { private List<int> _oceny = new List<int>(); public string Imie { get; set; } public List<int> Oceny { get { return _oceny; } } public void DodajOcene(int ocena) { if (ocena >= 1 && ocena <= 6) { _oceny.Add(ocena); } } // Właściwość OBLICZANA – średnia ocen public double SredniaOcen { get { if (_oceny.Count == 0) return 0; return _oceny.Average(); } } } // Użycie: var student = new Student { Imie = "Marta" }; student.DodajOcene(5); student.DodajOcene(4); student.DodajOcene(5); student.DodajOcene(3); Console.WriteLine($"{student.Imie}, średnia: {student.SredniaOcen:F2}"); // Marta, średnia: 4,25
Właściwości z wartościami domyślnymi
Od C# 6.0 możemy nadać właściwościom wartości domyślne bezpośrednio przy deklaracji.
public class Osoba { // Właściwości z wartościami domyślnymi public string Imie { get; set; } = "Nieznane"; public string Nazwisko { get; set; } = "Nieznane"; public DateTime DataUrodzenia { get; set; } = DateTime.Now; // Właściwość tylko do czytania, obliczana public string PelneImie { get { return $"{Imie} {Nazwisko}"; } } } // Użycie: var osoba = new Osoba(); // Wszystkie wartości domyślne Console.WriteLine(osoba.PelneImie); // Nieznane Nieznane osoba.Imie = "Paweł"; osoba.Nazwisko = "Nowak"; Console.WriteLine(osoba.PelneImie); // Paweł Nowak
Podsumowanie – kiedy czego używać?
| Sytuacja | Co użyć? | Przykład |
|---|---|---|
| Proste pole bez walidacji | Auto-property | public int Wiek { get; set; } |
| Pole z walidacją | Property z logiką w setterze | set { if (value > 0) _wiek = value; } |
| Dane tylko do czytania | Property z samym getterem | public string Numer { get; } |
| Obliczane wartości | Property z logiką w getterze | get { return _oceny.Average(); } |
| Kontrola dostępu | Private/protected setter | public decimal Saldo { get; private set; } |
Domyślnie używaj auto-properties. Dodawaj logikę (walidację, obliczenia) tylko gdy jest potrzebna.
Ćwiczenia dla uczniów
📝 Zadanie 1: Klasa Samochod
Stwórz klasę Samochod z właściwościami:
Marka– string, auto-propertyPredkosc– int, z walidacją: maksymalnie 300 km/hPaliwo– decimal, getter tylko (zmienia się w metodzieJedz)
Dodaj metodę Jedz(int km) – zmniejsza paliwo o km * 0.08 (8L/100km).
💡 Podpowiedź: Paliwo nie może spaść poniżej 0!
📝 Zadanie 2: Klasa Gracz
Stwórz klasę Gracz z właściwościami:
Nazwa– auto-propertyPunkty– int, getter publiczny, setter prywatnyPoziom– właściwość obliczana:Punkty / 100
Dodaj metodę ZdobadzPunkty(int punkty) – zwiększa punkty.
📝 Zadanie 3: Klasa Temperatura
Stwórz klasę Temperatura:
- Przechowuje temperaturę w Celsjuszach (pole prywatne)
Celsius– właściwość z walidacją (min -273.15°C)Fahrenheit– właściwość obliczana, tylko do czytania
Wzór: F = C × 9/5 + 32
⭐ Bonus: Dodaj właściwość Kelvin (obliczaną)
⭐ Zadanie 4: Klasa Prostokat
Stwórz klasę Prostokat z:
SzerokosciWysokosc– z walidacją (> 0)Pole– właściwość obliczana (tylko getter)Obwod– właściwość obliczana (tylko getter)CzyKwadrat– właściwość bool, obliczana
⭐⭐ Zadanie 5: Klasa KontoUzytkownika
Stwórz klasę KontoUzytkownika z:
Login– read-only (ustawiany tylko w konstruktorze)Email– z walidacją (musi zawierać @)Haslo– write-only (tylko setter, nie można odczytać!)DataRejestracji– auto-property z wartością domyślnąDateTime.NowDniOdRejestracji– właściwość obliczana
Dodaj metodę SprawdzHaslo(string haslo) zwracającą bool.