Referencje w C# – jak obiekty żyją w pamięci

Zrozumiesz różnicę między typami wartościowymi a referencyjnymi, nauczysz się unikać NullReferenceException i dowiesz się, dlaczego kopiowanie obiektów nie działa tak jak myślisz!

referencja wartość null pamięć NullReferenceException
01

Problem – dziwne zachowanie obiektów

Zanim wyjaśnimy czym jest referencja, zobaczmy dziwne zachowanie, które zaskakuje każdego początkującego programistę.

cs/DziwneZachowanie.cs C#
public class Osoba
{
    public string Imie;
    public int Wiek;
}

// Tworzę osobę
Osoba osoba1 = new Osoba();
osoba1.Imie = "Jan";
osoba1.Wiek = 25;

// "Kopiuję" do osoba2
Osoba osoba2 = osoba1;

// Zmieniam TYLKO osoba2
osoba2.Imie = "Anna";
osoba2.Wiek = 30;

// Co się wyświetli?
Console.WriteLine(osoba1.Imie);  // 🤔 ???
Console.WriteLine(osoba1.Wiek);  // 🤔 ???
😱 Zaskoczenie!

Wynik to:

Anna
30

Ale przecież zmieniałem tylko osoba2! Dlaczego osoba1 też się zmieniła?

Porównajmy to z zachowaniem liczb:

cs/NormalneLiczby.cs C#
// Z liczbami działa "normalnie"
int liczba1 = 10;
int liczba2 = liczba1;  // Kopia

liczba2 = 99;  // Zmieniam tylko liczba2

Console.WriteLine(liczba1);  // 10 ✅ Bez zmian!
Console.WriteLine(liczba2);  // 99

Dlaczego liczby zachowują się „normalnie”, a obiekty „dziwnie”? Odpowiedź: referencje.

02

Czym jest referencja?

Referencja to „adres” obiektu w pamięci komputera. Gdy tworzysz obiekt, zmienna nie przechowuje samego obiektu – przechowuje tylko informację o tym, gdzie ten obiekt się znajduje.

Analogia: Notes z adresami 📓

Wyobraź sobie, że masz notes z adresami znajomych:

  • Dom znajomego = obiekt w pamięci (prawdziwe dane)
  • Adres w notesie = referencja (wskazówka gdzie szukać)
  • Kartka w notesie = zmienna

Gdy „kopiujesz” adres do innego notesu, nie budujesz drugiego domu – masz tylko dwa adresy prowadzące do tego samego miejsca!

Analogia: Pilot do telewizora 📺

Inna analogia:

  • Telewizor = obiekt (prawdziwe urządzenie)
  • Pilot = referencja (sposób kontrolowania telewizora)

Możesz mieć dwa piloty do tego samego telewizora. Gdy użyjesz jednego pilota aby zmienić głośność, drugi pilot też „widzi” zmianę – bo oba kontrolują ten sam telewizor!

osoba1
Zmienna
(przechowuje adres)
Obiekt Osoba
Imie: „Jan”
Wiek: 25
adres: 0x1A2B

↑ Zmienna osoba1 zawiera adres (np. 0x1A2B), nie sam obiekt

03

Typy wartościowe (value types)

Typy wartościowe przechowują swoją wartość bezpośrednio w zmiennej. Gdy kopiujesz zmienną, kopiujesz samą wartość.

Typy wartościowe w C#

TypPrzykładOpis
intint x = 42;Liczba całkowita
doubledouble y = 3.14;Liczba zmiennoprzecinkowa
boolbool ok = true;Wartość logiczna
charchar c = 'A';Pojedynczy znak
decimaldecimal m = 9.99m;Precyzyjne obliczenia
structDateTime, PointStruktury (wartościowe)
cs/TypyWartosciowe.cs C#
// Typy wartościowe – kopiowanie WARTOŚCI

int a = 10;
int b = a;  // Kopiujemy WARTOŚĆ 10

b = 99;  // Zmieniamy tylko b

Console.WriteLine(a);  // 10 ✅ Bez zmian!
Console.WriteLine(b);  // 99

// To samo z innymi typami wartościowymi:
double x = 3.14;
double y = x;
y = 2.71;
Console.WriteLine(x);  // 3.14 ✅

bool flaga1 = true;
bool flaga2 = flaga1;
flaga2 = false;
Console.WriteLine(flaga1);  // True ✅
a
wartość: 10
→ kopia →
b
wartość: 10

↑ Każda zmienna ma swoją WŁASNĄ kopię wartości

Jak pudełka z przedmiotami 📦

Typy wartościowe to jak pudełka z przedmiotami w środku. Gdy „kopiujesz” pudełko, dostajesz nowe pudełko z kopią przedmiotu. Zmiana w jednym pudełku nie wpływa na drugie.

04

Typy referencyjne (reference types)

Typy referencyjne przechowują adres (referencję) do obiektu w pamięci. Gdy kopiujesz zmienną, kopiujesz tylko adres, nie sam obiekt!

Typy referencyjne w C#

TypPrzykładOpis
classnew Osoba()Wszystkie klasy
string"Hello"Łańcuchy znaków
arraynew int[5]Tablice
objectnew object()Klasa bazowa
interfaceIList<int>Interfejsy
delegateAction, FuncDelegaty
cs/TypyReferencyjne.cs C#
public class Osoba
{
    public string Imie;
    public int Wiek;
}

// Tworzymy obiekt
Osoba osoba1 = new Osoba();
osoba1.Imie = "Jan";
osoba1.Wiek = 25;

// "Kopiujemy" – ale tylko ADRES!
Osoba osoba2 = osoba1;  // Kopia REFERENCJI, nie obiektu!

// Zmieniamy przez osoba2
osoba2.Imie = "Anna";
osoba2.Wiek = 30;

// OBE zmienne wskazują na TEN SAM obiekt!
Console.WriteLine(osoba1.Imie);  // Anna 😱
Console.WriteLine(osoba1.Wiek);  // 30 😱
Console.WriteLine(osoba2.Imie);  // Anna
Console.WriteLine(osoba2.Wiek);  // 30
osoba1
adres: 0x1A2B
Obiekt Osoba
Imie: „Anna”
Wiek: 30
osoba2
adres: 0x1A2B

↑ Obie zmienne mają TEN SAM adres – wskazują na jeden obiekt!

Kluczowe zrozumienie!

Osoba osoba2 = osoba1; NIE tworzy kopii obiektu!

To tylko kopiuje adres – teraz masz dwie zmienne wskazujące na ten sam obiekt. Zmiana przez jedną zmienną jest widoczna przez drugą.

05

Wizualizacja w pamięci

Zobaczmy jak wyglądają typy wartościowe i referencyjne w pamięci komputera.

Typy wartościowe – każda zmienna ma własną kopię

txt/PamiecWartosciowa.txt Pamięć
int a = 10;
int b = a;
b = 99;

PAMIĘĆ (STOS):
┌─────────────┐
│ a: 10       │  ← Osobna komórka
├─────────────┤
│ b: 99       │  ← Osobna komórka
└─────────────┘

Zmiana b NIE wpływa na a!

Typy referencyjne – zmienne dzielą ten sam obiekt

txt/PamiecReferencyjna.txt Pamięć
Osoba osoba1 = new Osoba();
osoba1.Imie = "Jan";
Osoba osoba2 = osoba1;
osoba2.Imie = "Anna";

PAMIĘĆ:

STOS (zmienne):              STERTA (obiekty):
┌─────────────────┐          ┌─────────────────┐
│ osoba1: 0x1A2B ─┼─────────→│ Obiekt Osoba    │
├─────────────────┤          │ Imie: "Anna"    │
│ osoba2: 0x1A2B ─┼─────────→│ Wiek: 25        │
└─────────────────┘          └─────────────────┘

Obie zmienne wskazują na TEN SAM obiekt!

Tworzenie nowych obiektów – różne adresy

cs/RozneObiekty.cs C#
// Każdy NEW tworzy NOWY obiekt!
Osoba osoba1 = new Osoba();  // Nowy obiekt (adres: 0x1A2B)
osoba1.Imie = "Jan";

Osoba osoba2 = new Osoba();  // INNY nowy obiekt (adres: 0x3C4D)
osoba2.Imie = "Anna";

// Teraz to SĄ RÓŻNE obiekty!
osoba2.Imie = "Maria";

Console.WriteLine(osoba1.Imie);  // Jan ✅ Bez zmian!
Console.WriteLine(osoba2.Imie);  // Maria
txt/DwaObiekty.txt Pamięć
PAMIĘĆ:

STOS:                        STERTA:
┌─────────────────┐          ┌─────────────────┐
│ osoba1: 0x1A2B ─┼─────────→│ Obiekt Osoba    │
├─────────────────┤          │ Imie: "Jan"     │
│ osoba2: 0x3C4D ─┼───┐      └─────────────────┘
└─────────────────┘   │      
                      │      ┌─────────────────┐
                      └─────→│ Obiekt Osoba    │
                             │ Imie: "Maria"   │
                             └─────────────────┘

Różne adresy = różne obiekty!
Kluczowa zasada

Każde użycie new tworzy nowy obiekt z nowym adresem. Bez new – tylko kopiujesz adres istniejącego obiektu.

06

Porównanie: wartość vs referencja

Typy wartościowe

  • Przechowują: samą wartość
  • Kopiowanie: kopia wartości
  • Zmiana kopii: NIE wpływa na oryginał
  • Przykłady: int, double, bool, char, struct
  • Domyślna wartość: 0, false, '\0′

Typy referencyjne

  • Przechowują: adres obiektu
  • Kopiowanie: kopia adresu
  • Zmiana „kopii”: WPŁYWA na „oryginał”
  • Przykłady: class, string, array
  • Domyślna wartość: null
CechaWartościoweReferencyjne
Co w zmiennej?Sama wartośćAdres (referencja)
Gdzie w pamięci?Stos (stack)Sterta (heap)
Czy może być null?Nie (chyba że nullable)Tak
Porównanie ==Porównuje wartościPorównuje adresy*

* String jest wyjątkiem – porównuje zawartość, nie adresy.

07

null – brak obiektu

null oznacza „brak referencji” – zmienna nie wskazuje na żaden obiekt. To jak adres w notesie, który jest pusty.

cs/Null.cs C#
// Deklaracja bez inicjalizacji – domyślnie null
Osoba osoba1;  // null (niezainicjalizowana)

// Jawne przypisanie null
Osoba osoba2 = null;  // Jawnie: "nie ma obiektu"

// Po użyciu obiektu – "zapominamy" o nim
Osoba osoba3 = new Osoba();
osoba3.Imie = "Jan";
osoba3 = null;  // Teraz osoba3 już nie wskazuje na obiekt
Analogia: Pusty adres w notesie

null to jak pusta strona w notesie z adresami:

  • Strona istnieje (zmienna jest zadeklarowana)
  • Ale nie ma na niej żadnego adresu (nie wskazuje na obiekt)
  • Nie możesz „pójść” pod pusty adres!
osoba
null
(brak adresu)
∅ Nic
Brak obiektu
08

NullReferenceException – najczęstszy błąd!

Gdy próbujesz użyć obiektu przez zmienną, która jest null, dostajesz NullReferenceException – jeden z najczęstszych błędów w C#!

cs/NullReferenceException.cs C#
Osoba osoba = null;

// ❌ BŁĄD! Próba użycia null
osoba.Imie = "Jan";  // NullReferenceException!

// Komunikat błędu:
// System.NullReferenceException: Object reference not set to an instance of an object.
Co oznacza ten błąd?

„Próbujesz użyć obiektu, ale zmienna nie wskazuje na żaden obiekt (jest null).”

To jak próba zadzwonienia pod pusty numer telefonu – nie ma komu odebrać!

Jak uniknąć NullReferenceException?

cs/SprawdzanieNull.cs C#
Osoba osoba = null;

// Sposób 1: Sprawdzenie != null
if (osoba != null)
{
    osoba.Imie = "Jan";  // Bezpieczne!
}
else
{
    Console.WriteLine("Brak osoby!");
}

// Sposób 2: Operator ?. (null-conditional)
string imie = osoba?.Imie;  // Zwróci null jeśli osoba jest null

// Sposób 3: Operator ?? (null-coalescing)
string imie2 = osoba?.Imie ?? "Nieznane";  // Domyślna wartość

// Sposób 4: Pattern matching (C# 7+)
if (osoba is not null)
{
    Console.WriteLine(osoba.Imie);
}
OperatorSkładniaCo robi?
!= nullif (x != null)Klasyczne sprawdzenie
?.x?.PoleZwraca null jeśli x jest null
??x ?? domyslnaWartość domyślna gdy null
is not nullif (x is not null)Pattern matching
09

Sprawdzanie referencji

Możesz sprawdzić, czy dwie zmienne wskazują na ten sam obiekt (tę samą referencję).

cs/SprawdzanieReferencji.cs C#
Osoba osoba1 = new Osoba();
osoba1.Imie = "Jan";

Osoba osoba2 = new Osoba();  // NOWY obiekt!
osoba2.Imie = "Jan";

Osoba osoba3 = osoba1;  // Kopia REFERENCJI

// Sprawdzanie czy to TEN SAM obiekt (ta sama referencja)
Console.WriteLine(osoba1 == osoba2);  // False – różne obiekty!
Console.WriteLine(osoba1 == osoba3);  // True – ten sam obiekt!

// ReferenceEquals – jawne sprawdzenie referencji
Console.WriteLine(ReferenceEquals(osoba1, osoba2));  // False
Console.WriteLine(ReferenceEquals(osoba1, osoba3));  // True

// Sprawdzenie czy obiekt istnieje
Console.WriteLine(osoba1 != null);  // True
== dla obiektów

Dla większości klas, operator == porównuje referencje (czy to ten sam obiekt), nie zawartość!

Wyjątek: string – operator == porównuje zawartość tekstu.

10

Tablice są referencyjne!

Tablice w C# to typy referencyjne – kopiowanie zmiennej tablicowej kopiuje tylko referencję, nie elementy!

cs/TabliceReferencje.cs C#
int[] tablica1 = { 1, 2, 3 };

// "Kopiujemy" tablicę – ale to tylko kopia referencji!
int[] tablica2 = tablica1;

// Zmieniamy przez tablica2
tablica2[0] = 999;

// OBE tablice pokazują zmianę!
Console.WriteLine(tablica1[0]);  // 999 😱
Console.WriteLine(tablica2[0]);  // 999

// Czy to ten sam obiekt?
Console.WriteLine(tablica1 == tablica2);  // True

Jak NAPRAWDĘ skopiować tablicę?

cs/KopiowanieTablicy.cs C#
int[] original = { 1, 2, 3 };

// Sposób 1: Metoda Clone()
int[] kopia1 = (int[])original.Clone();

// Sposób 2: Array.Copy()
int[] kopia2 = new int[original.Length];
Array.Copy(original, kopia2, original.Length);

// Sposób 3: ToArray() z LINQ
int[] kopia3 = original.ToArray();

// Teraz zmiany w kopii nie wpływają na oryginał!
kopia1[0] = 999;
Console.WriteLine(original[0]);  // 1 ✅
11

String – specjalny przypadek

string jest typem referencyjnym, ale zachowuje się trochę jak typ wartościowy dzięki niemutowalności (immutability).

cs/StringSpecjalny.cs C#
string tekst1 = "Hello";
string tekst2 = tekst1;  // Kopia referencji

// Ale zmiana "tekst2" nie wpływa na tekst1!
tekst2 = "World";  // To tworzy NOWY obiekt string!

Console.WriteLine(tekst1);  // "Hello" ✅
Console.WriteLine(tekst2);  // "World"

// Porównanie == dla stringów porównuje ZAWARTOŚĆ
string a = "Test";
string b = "Test";
Console.WriteLine(a == b);  // True – ta sama zawartość!
Niemutowalność stringa

String jest niemutowalny (immutable) – nie możesz zmienić jego zawartości. Każda „modyfikacja” tworzy nowy obiekt:

string s = "Hello";
s = s + " World";  // Tworzy NOWY string "Hello World"
                   // Stary "Hello" pozostaje niezmieniony

Dlatego tekst2 = "World" nie zmienia oryginalnego stringa – tworzy nowy i przypisuje do tekst2 nową referencję.

12

Przekazywanie do metod

Gdy przekazujesz obiekt do metody, przekazujesz kopię referencji. Metoda może zmienić zawartość obiektu!

cs/PrzekazywanieDoMetod.cs C#
public class Osoba
{
    public string Imie;
    public int Wiek;
}

// Metoda przyjmująca obiekt
static void ZmienWiek(Osoba osoba)
{
    osoba.Wiek = 100;  // Zmienia ORYGINALNY obiekt!
}

// Użycie
Osoba jan = new Osoba();
jan.Imie = "Jan";
jan.Wiek = 25;

ZmienWiek(jan);

Console.WriteLine(jan.Wiek);  // 100 😱 Zmienione!

Porównanie: wartość vs referencja w metodach

cs/PorowanieWMetodach.cs C#
// Typ wartościowy – kopia wartości
static void ZmienLiczbe(int x)
{
    x = 999;  // Zmienia tylko LOKALNĄ kopię
}

int liczba = 10;
ZmienLiczbe(liczba);
Console.WriteLine(liczba);  // 10 ✅ Bez zmian!

// Typ referencyjny – kopia referencji (ten sam obiekt)
static void ZmienTablice(int[] tab)
{
    tab[0] = 999;  // Zmienia ORYGINALNĄ tablicę!
}

int[] tablica = { 1, 2, 3 };
ZmienTablice(tablica);
Console.WriteLine(tablica[0]);  // 999 😱 Zmienione!
Uwaga przy metodach!

Gdy przekazujesz obiekt lub tablicę do metody, metoda może modyfikować zawartość! To często pożądane zachowanie, ale może też prowadzić do błędów jeśli o tym zapomnisz.

13

Częste błędy

❌ Błąd 1: Myślenie że = kopiuje obiekt

❌ Źle rozumiane

Osoba osoba1 = new Osoba();
osoba1.Imie = "Jan";

Osoba osoba2 = osoba1;
// "Teraz mam dwa obiekty!"
// NIE! To ten sam obiekt!

✅ Prawda

Osoba osoba1 = new Osoba();
osoba1.Imie = "Jan";

Osoba osoba2 = osoba1;
// osoba1 i osoba2 wskazują
// na TEN SAM obiekt!

❌ Błąd 2: Nie sprawdzanie null

❌ Źle

Osoba osoba = null;
osoba.Imie = "Jan";
// NullReferenceException!

✅ Dobrze

Osoba osoba = null;
if (osoba != null)
{
    osoba.Imie = "Jan";
}

❌ Błąd 3: Myślenie że tablice kopiują elementy

❌ Źle

int[] tab1 = {1, 2, 3};
int[] tab2 = tab1;
// "tab2 to kopia!"
// NIE! To ta sama tablica!

✅ Dobrze

int[] tab1 = {1, 2, 3};
int[] tab2 = (int[])tab1.Clone();
// Teraz tab2 to PRAWDZIWA
// kopia tablicy!

❌ Błąd 4: Porównywanie obiektów przez ==

❌ Mylące

Osoba o1 = new Osoba();
o1.Imie = "Jan";
Osoba o2 = new Osoba();
o2.Imie = "Jan";

o1 == o2  // False!
// Różne obiekty mimo
// tej samej zawartości

✅ Świadome

// == porównuje REFERENCJE
// (czy to ten sam obiekt)

// Dla porównania zawartości
// użyj metody Equals()
// lub zdefiniuj własną
14

Podsumowanie

Kluczowe pojęcia
  • Referencja – „adres” obiektu w pamięci
  • Typ wartościowy – przechowuje wartość bezpośrednio (int, double, bool)
  • Typ referencyjny – przechowuje adres obiektu (class, array, string)
  • null – brak referencji (nie wskazuje na żaden obiekt)
  • NullReferenceException – próba użycia null

Porównanie typów

WartościoweReferencyjne
W zmiennejSama wartośćAdres obiektu
KopiowanieKopia wartościKopia adresu
Zmiana kopiiNie wpływaWpływa na oryginał!
Może być null?NieTak
Przykładyint, double, boolclass, array, string

Zasady do zapamiętania

cs/Zasady.cs C#
// 1. new tworzy NOWY obiekt
Osoba a = new Osoba();  // Nowy obiekt
Osoba b = new Osoba();  // INNY nowy obiekt

// 2. = kopiuje REFERENCJĘ, nie obiekt
Osoba c = a;  // c i a wskazują na TEN SAM obiekt

// 3. Zawsze sprawdzaj null przed użyciem
if (osoba != null) { /* bezpiecznie */ }

// 4. Tablice to typy referencyjne!
int[] kopia = (int[])original.Clone();  // Prawdziwa kopia
Zadania

Zadania praktyczne

📝 Zadanie 1: Przewiduj wynik

Co wyświetli się na ekranie? Odpowiedz BEZ uruchamiania kodu, potem sprawdź.

cs/Zadanie1.cs C#
public class Ksiazka
{
    public string Tytul;
    public int Strony;
}

Ksiazka ksiazka1 = new Ksiazka();
ksiazka1.Tytul = "Hobbit";
ksiazka1.Strony = 300;

Ksiazka ksiazka2 = ksiazka1;
ksiazka2.Strony = 350;

Console.WriteLine(ksiazka1.Strony);  // ???

💡 Podpowiedź: Czy ksiazka2 to kopia obiektu czy kopia referencji?

📝 Zadanie 2: Znajdź błąd

Ten kod rzuca wyjątek. Znajdź błąd i napraw go.

cs/Zadanie2.cs C#
public class Student
{
    public string Imie;
    public double Ocena;
}

Student student = null;
student.Imie = "Kowalski";

💡 Podpowiedź: Co oznacza null?

📝 Zadanie 3: Ile obiektów?

Ile obiektów klasy Auto powstaje w tym kodzie?

cs/Zadanie3.cs C#
Auto auto1 = new Auto();
Auto auto2 = new Auto();
Auto auto3 = auto1;
Auto auto4 = auto2;
Auto auto5 = auto3;

💡 Podpowiedź: Policz słowa new

⭐ Zadanie 4: Tablice

Napisz kod, który:

  1. Tworzy tablicę liczb {1, 2, 3, 4, 5}
  2. Tworzy PRAWDZIWĄ kopię tej tablicy
  3. Zmienia pierwszy element kopii na 999
  4. Wyświetla pierwszy element oryginału (powinien być nadal 1)

💡 Podpowiedź: Użyj Clone() lub Array.Copy()

⭐⭐ Zadanie 5: Bezpieczna metoda

Napisz metodę BezpiecznieWyswietl(Osoba osoba), która:

  • Sprawdza czy osoba nie jest null
  • Jeśli nie jest null – wyświetla imię i wiek
  • Jeśli jest null – wyświetla „Brak danych”

Przetestuj metodę z obiektem i z null.

💡 Podpowiedź: Użyj if (osoba != null) lub operatora ?.