Pola i modyfikatory dostępu w C# – „Schowki na dane”

Co to są pola w klasie?

Pola (fields) to zmienne zdefiniowane wewnątrz klasy, które przechowują dane obiektu. Można je porównać do „schowków” – każdy obiekt ma swoje własne kopie tych schowków z własnymi wartościami.

Analogia z życia codziennego

Wyobraź sobie szafkę ucznia w szkole:

  • Klasa = projekt szafki
  • Obiekt = konkretna szafka ucznia
  • Pola = przegródki w szafce (na książki, zeszyty, telefon)
  • Modyfikatory dostępu = kto może otwierać które przegródki
public class Uczen
{
    // To są pola klasy - "przegródki" każdego ucznia
    public string imie;           // Każdy może zobaczyć imię
    private string numerTelefonu; // Tylko sam uczeń ma dostęp
    protected double srednia;     // Uczeń i jego "krewni" (klasy pochodne)
}

Podstawowe typy pól

Pola instancji (zwykłe)

Każdy obiekt ma swoją własną kopię:

public class Samochod
{
    // Każdy samochód ma swoje własne wartości
    public string marka;
    public string model;
    public int rocznik;
    public double przebieg;
    public bool czySprawny;
}

// Każdy obiekt ma niezależne wartości pól
Samochod auto1 = new Samochod();
auto1.marka = "Toyota";
auto1.przebieg = 50000;

Samochod auto2 = new Samochod();
auto2.marka = "BMW";
auto2.przebieg = 80000;

Console.WriteLine(auto1.marka);    // "Toyota"
Console.WriteLine(auto2.marka);    // "BMW" - różne wartości!

Pola statyczne (wspólne dla wszystkich)

Jedna wartość dzielona między wszystkie obiekty:

public class Licznik
{
    public static int iloscObiektow = 0;  // Wspólne dla wszystkich!
    public string nazwa;                   // Każdy obiekt ma swoją własną
    
    public Licznik(string nazwa)
    {
        this.nazwa = nazwa;
        iloscObiektow++;  // Zwiększamy wspólny licznik
    }
}

// Używanie
Licznik obj1 = new Licznik("Pierwszy");
Licznik obj2 = new Licznik("Drugi");
Licznik obj3 = new Licznik("Trzeci");

Console.WriteLine(Licznik.iloscObiektow); // 3 - wspólna wartość!
// Uwaga: dostęp przez nazwę klasy, nie obiekt

Modyfikatory dostępu – „Poziomy bezpieczeństwa”

Modyfikatory dostępu kontrolują, kto może używać pól klasy.

public – „Otwarte dla wszystkich”

public class Osoba
{
    public string imie;    // Każdy może czytać i zmieniać
    public int wiek;
}

class Program
{
    static void Main()
    {
        Osoba osoba = new Osoba();
        
        // Każdy może używać pól public
        osoba.imie = "Jan";        // ✅ OK
        osoba.wiek = 25;           // ✅ OK
        
        Console.WriteLine(osoba.imie);  // ✅ OK
        Console.WriteLine(osoba.wiek);  // ✅ OK
    }
}

private – „Tylko dla mnie”

public class KontoBankowe
{
    public string numerKonta;     // Wszyscy mogą zobaczyć numer
    private double saldo;         // Tylko klasa może zmieniać saldo
    private string pin;           // Nikt z zewnątrz nie ma dostępu
    
    public KontoBankowe(string numer, double poczatkoweSaldo)
    {
        numerKonta = numer;
        saldo = poczatkoweSaldo;  // ? OK - jesteśmy w klasie
        pin = "0000";
    }
    
    public void WyswietlSaldo()
    {
        Console.WriteLine($"Saldo: {saldo} zł"); // ? OK - jesteśmy w klasie
    }
    
    public void Wplata(double kwota)
    {
        if (kwota > 0)
        {
            saldo += kwota;  // ? OK - metoda w klasie może zmieniać private
        }
    }
}

class Program
{
    static void Main()
    {
        KontoBankowe konto = new KontoBankowe("123456", 1000);
        
        konto.numerKonta = "654321";     // ? OK - public
        // konto.saldo = 5000;           // ? BŁĄD - private!
        // Console.WriteLine(konto.pin); // ? BŁĄD - private!
        
        konto.WyswietlSaldo();          // ? OK - public metoda
        konto.Wplata(500);              // ? OK - public metoda
    }
}

protected – „Dla rodziny”

public class Zwierze
{
    public string gatunek;        // Wszyscy widzą
    protected string dnaKod;      // Tylko zwierzę i jego "dzieci"
    private string tajnyKod;      // Tylko to zwierzę
    
    public Zwierze(string gatunek)
    {
        this.gatunek = gatunek;
        this.dnaKod = "ACGT-1234";   // ? OK - jesteśmy w klasie
        this.tajnyKod = "SECRET";    // ? OK - jesteśmy w klasie
    }
}

public class Pies : Zwierze  // Pies dziedziczy po Zwierze
{
    public string rasa;
    
    public Pies(string rasa) : base("Canis lupus")
    {
        this.rasa = rasa;
        
        // Dostęp do pól z klasy bazowej:
        Console.WriteLine(gatunek);    // ? OK - public
        Console.WriteLine(dnaKod);     // ? OK - protected (jesteśmy "dzieckiem")
        // Console.WriteLine(tajnyKod);  // ? BŁĄD - private (nawet dla dzieci!)
    }
}

class Program
{
    static void Main()
    {
        Pies pies = new Pies("Labrador");
        
        Console.WriteLine(pies.gatunek);  // ? OK - public
        // Console.WriteLine(pies.dnaKod);   // ? BŁĄD - protected (nie jesteśmy "rodziną")
        Console.WriteLine(pies.rasa);     // ? OK - public
    }
}

internal – „Dla tego projektu”

public class UstawieniaAplikacji
{
    public string nazwaAplikacji;        // Wszyscy widzą
    internal string sciezkaKonfiguracji; // Tylko klasy w tym samym projekcie
    private string hasloAdmina;          // Tylko ta klasa
}

// W tym samym projekcie:
class InnaKlasa
{
    public void TestDostep()
    {
        UstawieniaAplikacji ustawienia = new UstawieniaAplikacji();
        
        ustawienia.nazwaAplikacji = "MojaApp";        // ✅ OK - public
        ustawienia.sciezkaKonfiguracji = "C:\\config"; // ✅ OK - internal (ten sam projekt)
        // ustawienia.hasloAdmina = "admin123";        // ❌ BŁĄD - private
    }
}

Wizualizacja modyfikatorów dostępu

┌─────────────────────────────────────────────────────────┐
│                    WSZĘDZIE                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                 PROJEKT                             │ │
│  │  ┌─────────────────────────────────────────────────┐ │ │
│  │  │              HIERARCHIA KLAS                    │ │ │
│  │  │  ┌─────────────────────────────────────────────┐ │ │ │
│  │  │  │                TA KLASA                     │ │ │ │
│  │  │  │                                             │ │ │ │
│  │  │  │  private    - tylko tutaj                   │ │ │ │
│  │  │  └─────────────────────────────────────────────┘ │ │ │
│  │  │  protected - tutaj + klasy pochodne             │ │ │
│  │  └─────────────────────────────────────────────────┘ │ │
│  │  internal - tutaj + inne klasy w projekcie          │ │
│  └─────────────────────────────────────────────────────┘ │
│  public - tutaj + inne projekty + wszędzie              │
└─────────────────────────────────────────────────────────┘

Pola tylko do odczytu (readonly)

Pola readonly można ustawić tylko w konstruktorze:

public class Osoba
{
    public readonly string pesel;           // Można ustawić tylko raz
    public readonly DateTime dataUrodzenia; // Nie zmieni się po utworzeniu
    public string imie;                     // Można zmieniać w dowolnym momencie
    
    public Osoba(string pesel, DateTime dataUrodzenia, string imie)
    {
        this.pesel = pesel;            // ✅ OK - jesteśmy w konstruktorze
        this.dataUrodzenia = dataUrodzenia; // ✅ OK
        this.imie = imie;
    }
    
    public void ZmienDane(string noweImie)
    {
        imie = noweImie;                    // ✅ OK - zwykłe pole
        // pesel = "98765432101";           // ❌ BŁĄD - readonly można ustawić tylko w konstruktorze!
    }
}

Pola stałe (const)

Wartości const są ustalane w czasie kompilacji i nie mogą się zmienić:

public class Matematyka
{
    public const double PI = 3.14159265359;    // Stała matematyczna
    public const int SEKUNDY_W_MINUCIE = 60;   // Nigdy się nie zmieni
    public const string WERSJA_APLIKACJI = "1.0"; // Stała wersja
    
    public static readonly DateTime DataKompilacji = DateTime.Now; // Ustawiane przy uruchomieniu
    
    public void PrzykladUzycia()
    {
        double obwod = 2 * PI * 10;  // Używanie stałej
        Console.WriteLine($"Obwód: {obwod}");
        
        // PI = 3.14;  // ❌ BŁĄD - nie można zmieniać const!
    }
}

// Używanie stałych
double pole = Matematyka.PI * 5 * 5;  // Dostęp przez nazwę klasy

Różnica: const vs readonly vs static readonly

public class PorownaniePol
{
    // const - wartość ustalona w kodzie, nie można zmienić
    public const string CONST_POLE = "Nigdy się nie zmieni";
    
    // readonly - można ustawić w konstruktorze, potem niezmienne
    public readonly string readonlyPole;
    
    // static readonly - wspólne dla wszystkich obiektów, ustawiane raz
    public static readonly DateTime StaticReadonlyPole = DateTime.Now;
    
    public PorownaniePol(string wartosc)
    {
        readonlyPole = wartosc;  // ✅ OK - można ustawić w konstruktorze
        // CONST_POLE = "nowa wartość";  // ❌ BŁĄD!
    }
}

Praktyczne przykłady

Przykład 1: Klasa Student z różnymi poziomami dostępu

public class Student
{
    // Dane publiczne - każdy może zobaczyć
    public string imie;
    public string nazwisko;
    public string kierunek;
    
    // Dane chronione - tylko dla systemu szkoły
    internal string numerIndeksu;
    internal int rokStudiow;
    
    // Dane prywatne - tylko dla obiektu studenta
    private double sredniaOcen;
    private string hasloDoSystemu;
    
    // Stałe - nie zmieniają się
    public const int MIN_PUNKTY_ZALICZENIE = 50;
    public const int MAX_PUNKTY = 100;
    
    public Student(string imie, string nazwisko, string kierunek, string nrIndeksu)
    {
        this.imie = imie;
        this.nazwisko = nazwisko;
        this.kierunek = kierunek;
        this.numerIndeksu = nrIndeksu;
        this.rokStudiow = 1;
        this.sredniaOcen = 0.0;
        this.hasloDoSystemu = GenerujHaslo();
    }
    
    // Publiczne metody do dostępu do prywatnych danych
    public double PobierzSrednia()
    {
        return sredniaOcen;
    }
    
    public void DodajOcene(double ocena)
    {
        if (ocena >= 2.0 && ocena <= 5.0)
        {
            // Logika obliczania nowej średniej
            sredniaOcen = (sredniaOcen + ocena) / 2;
        }
    }
    
    public bool CzyZaliczyl()
    {
        return sredniaOcen * 20 >= MIN_PUNKTY_ZALICZENIE; // Przeliczenie na punkty
    }
    
    private string GenerujHaslo()
    {
        return $"{imie.ToLower()}{numerIndeksu.Substring(0, 3)}";
    }
}

Przykład 2: Klasa z polami statycznymi

public class LicznikObiektow
{
    // Pola statyczne - wspólne dla wszystkich obiektów
    public static int lacznie = 0;
    private static int nastepnyId = 1;
    
    // Pola instancji - każdy obiekt ma swoje
    public readonly int id;
    public string nazwa;
    
    public LicznikObiektow(string nazwa)
    {
        this.id = nastepnyId++;  // Każdy obiekt dostaje unikalny ID
        this.nazwa = nazwa;
        lacznie++;               // Zwiększamy licznik wszystkich obiektów
    }
    
    public static void WyswietlStatystyki()
    {
        Console.WriteLine($"Utworzonych obiektów: {lacznie}");
        Console.WriteLine($"Następny ID: {nastepnyId}");
    }
}

// Używanie
LicznikObiektow obj1 = new LicznikObiektow("Pierwszy");
LicznikObiektow obj2 = new LicznikObiektow("Drugi");
LicznikObiektow obj3 = new LicznikObiektow("Trzeci");

Console.WriteLine(obj1.id);    // 1
Console.WriteLine(obj2.id);    // 2
Console.WriteLine(obj3.id);    // 3

LicznikObiektow.WyswietlStatistyki(); // "Utworzonych obiektów: 3, Następny ID: 4"

Najczęstsze błędy i dobre praktyki

Błąd 1: Wszystko public

// ŹLE - wszystko publiczne!
public class ZleKonto
{
    public double saldo;        // Każdy może zmienić saldo!
    public string pin;          // PIN widoczny dla wszystkich!
    public bool czyAktywne;
}

// DOBRZE - kontrolowany dostęp
public class DobreKonto
{
    private double saldo;       // Chronione saldo
    private string pin;         // Chroniony PIN
    public bool czyAktywne;     // OK - może być publiczne
    
    // Metody do bezpiecznego dostępu
    public double PobierzSaldo() { return saldo; }
    public bool Wyplata(double kwota, string podanyPin)
    {
        if (podanyPin == pin && kwota <= saldo)
        {
            saldo -= kwota;
            return true;
        }
        return false;
    }
}

Błąd 2: Zapomnienie o inicjalizacji

public class Osoba
{
    public string imie;     // Domyślnie null - może powodować błędy!
    public int wiek;        // Domyślnie 0 - OK
    public bool aktywny;    // Domyślnie false - OK
    
    // LEPIEJ - z inicjalizacją
    public string imie2 = "";        // Puste, ale nie null
    public List<string> hobby = new List<string>(); // Pusta lista, ale nie null
}

Dobre praktyki

public class DobrePraktyki
{
    // 1. Używaj znaczących nazw
    public string nazwaUzytkownika;     // ✅ DOBRZE
    // public string n;                 // ❌ ŹLE
    
    // 2. Prywatne pola z podkreślnikiem (konwencja)
    private string _haslo;              // ✅ Popularna konwencja
    private int _licznikProb;
    
    // 3. Stałe wielkimi literami
    public const double STAWKA_VAT = 0.23;     // ✅ DOBRZE
    // public const double vat = 0.23;          // ❌ ŹLE
    
    // 4. Readonly dla danych, które się nie zmieniają
    public readonly DateTime dataRejestracji = DateTime.Now;
    
    // 5. Protected tylko gdy planujemy dziedziczenie
    protected string bazoweDane;       // ✅ Jeśli klasa będzie bazowa
}

Kluczowe wnioski

  1. Pola przechowują stan obiektu – dane obiektu
  2. public – dostęp z wszędzie (używać ostrożnie!)
  3. private – dostęp tylko z tej klasy (najbezpieczniejsze)
  4. protected – dostęp z tej klasy + klasy pochodne
  5. internal – dostęp z tego projektu
  6. static – pole wspólne dla wszystkich obiektów klasy
  7. readonly – można ustawić tylko w konstruktorze
  8. const – wartość stała ustalona w kodzie
  9. Encapsulation – ukrywanie implementacji, udostępnianie interfejsu

Do zapamiętania

„Pola = szuflady obiektu” „private domyślnie, public tylko gdy naprawdę trzeba” „const = nigdy się nie zmieni, readonly = można ustawić raz” „static = jeden dla wszystkich, zwykłe = każdy ma swoje”

Zadania do przećwiczenia

Zadanie 1: Klasa z różnymi modyfikatorami
Napisz klasę Pracownik z polami:

  • imie (publiczne)
  • nazwisko (publiczne)
  • pensja (prywatne)
  • stanowisko (internal)
  • dataZatrudnienia (readonly)
  • MINIMALNA_PENSJA (const = 3000)

Dodaj konstruktor i metody do bezpiecznego dostępu do pensji.

Zadanie 2: Pola statyczne

Napisz klasę Produkt z polami statycznymi:

  • iloscWszystkichProduktow – ile produktów utworzono
  • nastepnyKod – kolejny numer produktu

Każdy nowy produkt powinien dostać unikalny kod.

Zadanie 3: Znajdź błędy

public class TestKlasa
{
    public const string nazwa;           // Błąd 1
    private readonly int liczba;         // Błąd 2
    public static wiek = 25;            // Błąd 3
    protected string dane = "test";
    
    public void TestMetoda()
    {
        nazwa = "Nowa nazwa";           // Błąd 4
        liczba = 100;                   // Błąd 5
    }
}

Zadanie 4: Refaktoryzacja

Popraw tę klasę stosując dobre praktyki:

public class zleklasa
{
    public string n;
    public string s;
    public double p;
    public bool a;
    public string haslo;
    public double saldo;
}