Menu

Klasy i obiekty w C#: pola, metody, new i referencje

Jak zadeklarować klasę w C#, dodać do niej pola i metody, tworzyć obiekty przez new i co oznacza, że klasa jest typem referencyjnym: współdzielone obiekty, null i NullReferenceException, ToString oraz inicjalizatory obiektów.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Klasa to typ, który definiujesz sam: łączy dane (pola) z kodem, który na tych danych działa (metodami). Obiekt to jedna instancja klasy, tworzona w czasie działania programu przez new. Jedna klasa, wiele obiektów, każdy z własnymi wartościami.

Deklarowanie klasy i tworzenie obiektów

Deklaracja klasy wymienia jej składowe w nawiasach klamrowych. Pola przechowują dane, metody zachowanie. new ClassName() alokuje obiekt i daje ci referencję do niego.

Wynik:

Maya: 120.50
Omar: 40
Withdraw 200 succeeded: False

Każdy obiekt ma własne Owner i Balance. Wewnątrz metody Balance oznacza "saldo obiektu, na którym wywołano tę metodę", dlatego maya.Deposit i omar.Deposit zmieniają różne liczby. Ten niejawny obiekt jest dostępny jako this, gdy trzeba go nazwać.

Pola, którym nie przypiszesz wartości, zaczynają od wartości domyślnej: 0 dla liczb, false dla bool i null dla napisów oraz innych typów klasowych.

Składowe są tu public, żeby Main miało do nich dostęp. Prawdziwe klasy zwykle trzymają pola jako private i udostępniają zamiast nich właściwości, a reguły opisuje strona o modyfikatorach dostępu. Klasa może też deklarować konstruktor, który ustawia pola przy tworzeniu obiektu; omawia to strona o konstruktorach.

Klasa jest typem referencyjnym

Zmienna typu klasowego nie zawiera obiektu. Zawiera referencję do obiektu, który żyje na stercie. Przypisanie zmiennej kopiuje referencję, więc dwie zmienne mogą wskazywać ten sam obiekt.

Wynik:

5
6
True
False

Widać tu trzy konsekwencje:

  • Zmiana obiektu przez second jest widoczna przez first, bo obiekt jest tylko jeden.
  • Przekazanie obiektu do metody przekazuje referencję. Metoda może zmienić pola obiektu, a kod wywołujący to zobaczy. (Przypisanie nowego obiektu do parametru wewnątrz metody nie wpływa na zmienną kodu wywołującego; do tego potrzeba ref.)
  • == na dwóch instancjach klasy domyślnie porównuje referencje: "czy to ten sam obiekt?", a nie "czy mają te same wartości?". lookalike ma identyczne dane, a mimo to jest innym obiektem.

Jeśli chcesz kopiowania przy przypisaniu i porównywania wartości, zajrzyj do struktur, które są typami wartościowymi, albo do rekordów, które porównują się po wartościach.

null i NullReferenceException

Zmienna referencyjna może przechowywać null, czyli "brak obiektu". Użycie składowej przez null rzuca NullReferenceException, najczęstszy wyjątek czasu wykonania w kodzie C#.

Wynik:

True
no city on file
NullReferenceException: Address was never set
Lisbon

Rozwiązaniem prawie nigdy nie jest łapanie wyjątku. Ustal, dlaczego referencja jest null, i albo utwórz obiekt wcześniej, albo sprawdź go przed użyciem, albo użyj operatora warunkowego null ?., który zatrzymuje się i daje null zamiast rzucać wyjątek. ?? dostarcza wtedy wartość zastępczą. Oba operatory są omówione w typach nullable.

Nadpisywanie ToString

Każda klasa dziedziczy po object, który udostępnia ToString(). Odziedziczona wersja zwraca pełną nazwę typu (razem z przestrzenią nazw, jeśli typ w jakiejś jest), a to rzadko jest to, co chcesz wypisać. Nadpisz ją:

Wynik:

Plain
Mug (8.50)
In cart: Mug (8.50)
Total line: Mug (8.50)

Console.WriteLine, interpolacja napisów i + z napisem same wywołują ToString(). Pokazuje go też debuger, więc nadpisanie jest przydatne nawet wtedy, gdy sam nigdy nie wypisujesz obiektu.

Inicjalizatory obiektów

Inicjalizator obiektu ustawia publiczne pola i właściwości zaraz po utworzeniu obiektu, w tym samym wyrażeniu. Czyta się go lepiej niż listę przypisań i dobrze sprawdza się wewnątrz literałów kolekcji.

Wynik:

A12: 25 (0 extras)
A13: 40 (2 extras)

Dwa szczegóły w drugim bilecie. Price = 40m nadpisuje 25m z inicjalizatora pola, bo inicjalizator obiektu działa po zbudowaniu obiektu. Extras = { "Parking", "Poster" } nie ma new: wywołuje Add na liście, którą pole już przechowuje, zamiast ją zastępować.

Inicjalizator obiektu nie jest konstruktorem. Może ustawiać tylko składowe dostępne z kodu wywołującego i nie może wymusić, że składowa w ogóle zostanie ustawiona. Gdy obiekt bez pewnych wartości jest niepoprawny, wymagaj ich w konstruktorze.

Klasy częściowe

Słowo kluczowe partial dzieli jedną klasę na kilka plików. Kompilator łączy części w jedną klasę.

// Invoice.cs
public partial class Invoice
{
    public decimal Subtotal;
    public decimal Total() => Subtotal + Tax();
}

// Invoice.Tax.cs
public partial class Invoice
{
    private decimal Tax() => Subtotal * 0.2m;
}

Każda część musi mieć partial, być w tej samej przestrzeni nazw i w tym samym projekcie. Typowe zastosowanie to kod generowany: projektanty WinForms, generatory źródeł i niektóre ORM-y piszą jedną część, a drugą zostawiają tobie, więc ponowne generowanie nigdy nie nadpisuje twojego kodu. Dzielenie ręcznie pisanej klasy na pliki tylko po to, żeby była krótsza, zwykle ukrywa klasę, która powinna być dwiema klasami.

Częste błędy

  • Używanie obiektu przed jego utworzeniem. Pole typu klasowego jest null, dopóki nie przypiszesz mu new .... Pierwszy dostęp do składowej rzuca NullReferenceException.
  • Oczekiwanie, że przypisanie kopiuje. var b = a; dla klasy daje drugą nazwę tego samego obiektu. Gdy potrzebujesz niezależnego obiektu, utwórz nowy i skopiuj wartości.
  • Porównywanie obiektów przez == w oczekiwaniu na porównanie wartości. Dla klas porównuje referencje, chyba że klasa przeciąża == (jak robi to string). Porównaj odpowiednie pola albo nadpisz Equals.
  • Wypisanie obiektu daje nazwę jego typu. Nadpisz ToString().
  • Deklarowanie wszystkiego jako public. Działa w małych przykładach, ale pozwala dowolnemu kodowi wprowadzić obiekt w niepoprawny stan, na przykład ujemne saldo. Trzymaj pola jako prywatne i zmieniaj je przez metody, które pilnują reguł.

Najczęściej zadawane pytania

Czym jest klasa w C#?

Klasa to typ, który definiujesz sam: nazwana grupa danych (pól i właściwości) i zachowania (metod). class Account { public decimal Balance; public void Deposit(decimal amount) { Balance += amount; } } deklaruje taką klasę. Klasa jest planem; nic nie robi, dopóki nie utworzysz z niej obiektów przez new.

Czym różni się klasa od obiektu w C#?

Klasa to definicja, napisana raz w kodzie źródłowym. Obiekt to jedna jej instancja, tworzona w czasie działania programu przez new, z własną kopią każdego pola instancji. Dwukrotne new Account() daje dwa obiekty tej samej klasy z niezależnymi saldami.

Jak utworzyć obiekt w C#?

Użyj new, po którym podajesz nazwę klasy i argumenty konstruktora: var account = new Account();. Publiczne pola i właściwości możesz ustawić w tym samym wyrażeniu przez inicjalizator obiektu: new Account { Owner = "Maya" }. Zmienna przechowuje referencję do nowego obiektu, a nie sam obiekt.

Skąd się bierze NullReferenceException w C#?

Użycie składowej (.Name, .Length, wywołanie metody) przez zmienną, która przechowuje null, więc nie ma obiektu, na którym można działać. Typowe przyczyny to pole, któremu nigdy nie przypisano wartości (pola typów klasowych zaczynają jako null), wyszukiwanie, które zwróciło null, albo metoda zwracająca null przy niepowodzeniu. Sprawdź to przez if (x != null), użyj x?.Name albo upewnij się, że obiekt jest utworzony przed użyciem.

Jak nadpisać ToString w C#?

Zadeklaruj w klasie public override string ToString() i zwróć tekst, jaki chcesz, na przykład public override string ToString() => $"{Name} ({Price:F2})";. Bez tego Console.WriteLine(obj) i interpolacja napisów wypisują nazwę klasy, bo to właśnie zwraca object.ToString().

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ