Enum i struct — nazwane stany oraz proste wartości
Zamiast pamiętać, że 2 oznacza oddany sprzęt, użyjemy nazwy. Poznamy też mały typ reprezentujący punkt planszy.
Czego się dziś nauczysz
- Wyjaśnisz, dlaczego liczba i napis są złym zapisem stanu, i wskażesz, co się psuje w każdym z nich.
- Zadeklarujesz typ wyliczeniowy, także z własnymi numerami pozycji i mniejszym typem w tle.
- Zamienisz wartość wyliczeniową na liczbę i tekst oraz w drugą stronę, ze sprawdzeniem poprawności.
- Napiszesz
switchpo wartości wyliczeniowej i uzasadnisz obecność gałęzi_. - Przejdziesz wszystkie pozycje pętlą i wczytasz wartość od użytkownika przez
Enum.TryParse. - Zbudujesz zestaw uprawnień na
enumz[Flags]i sprawdzisz je metodąHasFlag. - Zadeklarujesz prostą strukturę i wskażesz trzy rzeczy, których
structnie może. - Wyjaśnisz, dlaczego przypisanie struktury tworzy kopię, a przypisanie obiektu klasy — nie.
Liczba albo napis — i oba źle
Wypożyczalnia sprzętu z lekcji 48 musi pamiętać, w jakim stanie jest każda pozycja: dostępna, wypożyczona, w naprawie albo wycofana. Pierwszy pomysł to liczba:
class Sprzet
{
public string Nazwa { get; }
public int Stan { get; set; } // 0 - dostepny, 1 - wypozyczony, 2 - w naprawie...
public Sprzet(string nazwa)
{
Nazwa = nazwa;
Stan = 0;
}
}
Sprzet s = new Sprzet("Laptop Dell");
s.Stan = 2;
if (s.Stan == 1)
{
Console.WriteLine("nie mozna wypozyczyc");
}
Program działa i jest nieczytelny. Za trzy tygodnie nikt — Ty też nie — nie będzie pamiętał, czy 2 oznaczało naprawę, czy wycofanie. Komentarz przy deklaracji pomaga, ale nie pojawia się w tych trzydziestu miejscach, gdzie potem porównujesz liczby.
Drugi pomysł to napis:
public string Stan { get; set; }
s.Stan = "w naprawie";
if (s.Stan == "wnaprawie") // literowka - warunek nigdy nie bedzie prawdziwy
{
// ...
}
Czytelniej, ale teraz łatwo o literówkę, a kompilator nie ma jak jej zauważyć — "wnaprawie" to poprawny napis. Do tego "W naprawie", "w naprawie" i "W NAPRAWIE" to trzy różne wartości.
| Zapis stanu | Czytelny? | Kompilator pilnuje literówek? | Da się wypisać wprost? |
|---|---|---|---|
liczba int | nie | nie | nie |
napis string | tak | nie | tak |
const int | tak | częściowo | nie |
| typ wyliczeniowy | tak | tak | tak |
Czego szukamy
Chcemy typu, który ma z góry ustaloną, zamkniętą listę wartości — takich, że kompilator odrzuci wszystko poza nią, a wypisanie da nazwę, nie liczbę. W C# nazywa się to typem wyliczeniowym, słowo kluczowe enum.
Zamknięta lista wartości
Deklaracja jest krótsza od wszystkiego, co pisałeś w tym bloku:
enum Stan
{
Dostepny,
Wypozyczony,
WNaprawie,
Wycofany
}
Od tej chwili Stan jest pełnoprawnym typem, takim jak int czy string:
Stan s = Stan.WNaprawie;
Console.WriteLine(s); // WNaprawie - nazwa, nie liczba
Console.WriteLine($"stan: {s}"); // dziala w interpolacji
if (s == Stan.Wypozyczony)
{
Console.WriteLine("ktos ma to u siebie");
}
// s = 5; // BLAD CS0029
// s = Stan.Zniszczony; // BLAD CS0117 - nie ma takiej nazwy
WNaprawie
stan: WNaprawie
Dwa zakomentowane wiersze to dokładnie to, czego brakowało w wersji z liczbą i napisem: kompilator odrzuca każdą wartość, której nie ma na liście, i wyłapuje literówkę w nazwie stanu.
Za nazwami stoją liczby
Każda pozycja w enum ma wartość liczbową. Domyślnie pierwsza dostaje 0 i dalej po jednym:
Console.WriteLine((int)Stan.Dostepny); // 0
Console.WriteLine((int)Stan.Wypozyczony); // 1
Console.WriteLine((int)Stan.WNaprawie); // 2
Console.WriteLine((int)Stan.Wycofany); // 3
Numery można też podać samemu — przydaje się, gdy liczby mają znaczenie poza programem, na przykład w pliku albo w bazie:
enum Priorytet
{
Niski = 1,
Sredni = 5,
Wysoki = 10
}
enum KodBledu : byte // mniejszy typ w tle, gdy wartosci sa male
{
Brak = 0,
BrakPliku = 4,
BrakDostepu = 5
}
Pozycja o wartości 0 jest szczególna
Pole typu enum w nowym obiekcie ma wartość 0 — nawet jeśli żadna pozycja nie ma takiej liczby. Dlatego pierwsza pozycja powinna być tą, która ma sens jako stan początkowy: Dostepny, Nieznany, Brak. W typie Priorytet powyżej brak pozycji o wartości 0, więc Priorytet p; w polu klasy da wartość, która nie ma nazwy i wypisze się jako 0.
Zamiana w obie strony
Stan s = Stan.WNaprawie;
int liczba = (int)s; // enum -> int
Stan znowu = (Stan)liczba; // int -> enum
string nazwa = s.ToString(); // enum -> tekst
Console.WriteLine($"{liczba} / {znowu} / {nazwa}");
Stan dziwny = (Stan)99; // UWAGA: to sie kompiluje i dziala!
Console.WriteLine(dziwny); // 99 - nie ma takiej nazwy
Console.WriteLine(Enum.IsDefined(dziwny)); // False
2 / WNaprawie / WNaprawie
99
False
Jedna dziura w szczelności
Rzutowanie (Stan)99 nie jest błędem — kompilator zakłada, że wiesz, co robisz. Dlatego przy wartościach pochodzących z zewnątrz (plik, konsola, baza) zawsze sprawdzaj je metodą Enum.IsDefined albo wczytuj przez Enum.TryParse. Wewnątrz programu, gdzie przypisujesz tylko nazwy, problemu nie ma.
Pole klasy, switch i wczytywanie
enum najczęściej trafia do pola klasy. Wtedy obiekt pilnuje swoich stanów sam:
class Sprzet
{
public string Nazwa { get; }
public Stan Stan { get; private set; }
public Sprzet(string nazwa)
{
Nazwa = nazwa;
Stan = Stan.Dostepny;
}
public bool Wypozycz(string komu)
{
if (Stan != Stan.Dostepny)
{
Console.WriteLine($"{Nazwa}: nie mozna wypozyczyc, stan {Stan}");
return false;
}
Stan = Stan.Wypozyczony;
Console.WriteLine($"{Nazwa}: wypozyczono dla {komu}");
return true;
}
public void OddajDoNaprawy()
{
Stan = Stan.WNaprawie;
}
}
switch po wartości wyliczeniowej
To najczęstsze użycie enum — czytelne rozgałęzienie bez ani jednej liczby:
static string Komunikat(Stan s)
{
return s switch
{
Stan.Dostepny => "mozna wypozyczyc",
Stan.Wypozyczony => "u kogos w domu",
Stan.WNaprawie => "w serwisie",
Stan.Wycofany => "zdjete z inwentarza",
_ => "stan nieznany"
};
}
Po co gałąź _
Kompilator nie wymaga wymienienia wszystkich pozycji, bo — jak widziałeś — do zmiennej typu enum da się wcisnąć liczbę spoza listy. Dlatego gałąź _ zostaw zawsze. Zyskujesz też drugą rzecz: gdy dopiszesz nową pozycję do enum i zapomnisz o tym switch, program nie wysypie się wyjątkiem, tylko zwróci „stan nieznany”.
Przejście po wszystkich pozycjach
foreach (Stan s in Enum.GetValues<Stan>())
{
Console.WriteLine($"{(int)s,2} {s,-12} {Komunikat(s)}");
}
0 Dostepny mozna wypozyczyc
1 Wypozyczony u kogos w domu
2 WNaprawie w serwisie
3 Wycofany zdjete z inwentarza
Wczytanie od użytkownika
Convert.ToInt32 tu nie wystarczy — potrzebna jest metoda, która rozumie nazwy:
Console.Write("Podaj stan (Dostepny/Wypozyczony/WNaprawie/Wycofany): ");
string? wpisane = Console.ReadLine();
if (Enum.TryParse<Stan>(wpisane, true, out Stan wynikStanu))
{
Console.WriteLine($"rozpoznano: {wynikStanu}");
}
else
{
Console.WriteLine("nie rozpoznano stanu");
}
Drugi argument true znaczy „nie zwracaj uwagi na wielkość liter”, więc wnaprawie też zadziała. Metoda zwraca bool — dokładnie tak jak int.TryParse z lekcji 12.
Pułapka TryParse
Enum.TryParse<Stan>("7", out s) zwraca true i ustawia (Stan)7, choć nie ma takiej pozycji — bo metoda przyjmuje także zapis liczbowy. Jeśli ma to znaczenie, dodaj po udanym rozpoznaniu sprawdzenie Enum.IsDefined(s).
Zestaw wartości w jednej zmiennej
Zwykły enum trzyma jedną wartość z listy. Czasem potrzebny jest zestaw: uczeń może mieć dostęp do czytania i zapisu jednocześnie. Do tego służy enum z atrybutem [Flags] i wartościami będącymi kolejnymi potęgami dwójki.
[Flags]
enum Dostep
{
Brak = 0,
Czytanie = 1, // 0001
Zapis = 2, // 0010
Usuwanie = 4, // 0100
Drukowanie = 8 // 1000
}
Zestaw składa się operatorem |, a sprawdza metodą HasFlag — operatory bitowe znasz z lekcji 10:
Dostep uczen = Dostep.Czytanie | Dostep.Drukowanie;
Console.WriteLine(uczen); // Czytanie, Drukowanie
Console.WriteLine((int)uczen); // 9 (1 + 8)
Console.WriteLine(uczen.HasFlag(Dostep.Zapis)); // False
Console.WriteLine(uczen.HasFlag(Dostep.Czytanie)); // True
uczen = uczen | Dostep.Zapis; // dodanie uprawnienia
Console.WriteLine(uczen); // Czytanie, Zapis, Drukowanie
uczen = uczen & ~Dostep.Drukowanie; // odebranie uprawnienia
Console.WriteLine(uczen); // Czytanie, Zapis
Czytanie, Drukowanie
9
False
True
Czytanie, Zapis, Drukowanie
Czytanie, Zapis
| Operacja | Zapis | Znaczenie |
|---|---|---|
| dodaj uprawnienie | d = d | Dostep.Zapis; | zapala bit |
| odbierz uprawnienie | d = d & ~Dostep.Zapis; | gasi bit |
| sprawdź uprawnienie | d.HasFlag(Dostep.Zapis) | zwraca bool |
| przełącz uprawnienie | d = d ^ Dostep.Zapis; | odwraca bit |
| brak uprawnień | d == Dostep.Brak | porównanie z pozycją o wartości 0 |
Wartości muszą być potęgami dwójki
Gdyby Usuwanie miało wartość 3 zamiast 4, zestaw Czytanie | Zapis (1 + 2 = 3) byłby nieodróżnialny od Usuwanie. Dlatego w enum z [Flags] kolejne pozycje to 1, 2, 4, 8, 16, 32… Sam [Flags] tego nie pilnuje — wpływa tylko na to, jak działa ToString().
Mała wartość zamiast obiektu
Drugi typ tej lekcji rozwiązuje inny problem. Pozycja pionka na planszy to dwie liczby — X i Y. Klasa by tu działała, ale jest jak młot na muchę: obiekt klasy zawsze leży osobno w pamięci, a zmienna trzyma tylko odwołanie do niego (lekcja 43).
Do małych, prostych wartości C# ma struct. Deklaracja wygląda prawie jak klasa:
struct Punkt
{
public int X { get; }
public int Y { get; }
public Punkt(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
}
Punkt a = new Punkt(3, 4);
Punkt b = a; // KOPIA calej wartosci, nie drugie odwolanie
Console.WriteLine(a); // (3, 4)
Console.WriteLine(b); // (3, 4)
Console.WriteLine(a.Equals(b));// True - porownuje pola, nie adresy
Punkt zero = default; // (0, 0) - pola dostaja wartosci domyslne
Console.WriteLine(zero);
(3, 4)
(3, 4)
True
(0, 0)
To najważniejsza różnica i wrócimy do niej dokładnie w lekcji 50: struct jest typem wartościowym. Przypisanie kopiuje całą zawartość, tak jak przy int. W klasie przypisanie dałoby drugą nazwę na ten sam obiekt.
Czego struct nie może
| Element | class | struct |
|---|---|---|
| dziedziczenie po innym typie | tak | nie (CS0527 — tylko interfejsy) |
| może być klasą bazową | tak | nie |
| implementowanie interfejsów | tak | tak |
virtual i abstract | tak | nie |
przesłonięcie ToString() | tak | tak |
| konstruktor bez parametrów | tak | tak, od C# 10 — ale default go pomija |
wartość null | tak | nie — potrzebne Punkt? |
| zachowanie przy przypisaniu | drugie odwołanie | kopia |
Znasz już wiele struktur
Prawie wszystko, czym posługujesz się od pierwszych lekcji, to struktury: int, double, bool, char, decimal, DateTime. Dlatego int a = b; kopiuje liczbę, a nie tworzy drugiej nazwy na tę samą. Wyjątkiem jest string — to klasa, choć zachowuje się podobnie dzięki niezmienności (lekcja 08).
Kiedy struct, a kiedy class
Reguła praktyczna: struct tylko wtedy, gdy typ opisuje jedną małą wartość (do kilkunastu bajtów), jest niezmienny i ma sens porównywania po zawartości. Pozycja, kolor, zakres dat, kwota z walutą. Wszystko inne — class. Jeśli się wahasz, wybierz class: to jest właściwa domyślna odpowiedź. Lekcja 50 pokazuje, co dokładnie idzie nie tak przy złym wyborze.
Magazyn wypożyczalni
Magazyn wypożyczalni: trzy typy wyliczeniowe (zwykły, z własnymi numerami i z [Flags]), jedna struktura na numer półki i klasa, która ich używa.
// Program.cs - pelny plik
List<Sprzet> magazyn = new List<Sprzet>
{
new Sprzet("Laptop Dell", Kategoria.Komputer, new Punkt(1, 2)),
new Sprzet("Projektor Epson", Kategoria.Prezentacja, new Punkt(1, 5)),
new Sprzet("Drukarka HP", Kategoria.Komputer, new Punkt(3, 1)),
new Sprzet("Mikroskop", Kategoria.Laboratorium, new Punkt(4, 4))
};
Console.WriteLine("=== stan magazynu ===");
Pokaz(magazyn);
Console.WriteLine();
Console.WriteLine("=== operacje ===");
magazyn[0].Wypozycz("Kasia Nowak");
magazyn[0].Wypozycz("Marek Lis"); // juz wypozyczony
magazyn[1].OddajDoNaprawy();
magazyn[3].Wycofaj();
Console.WriteLine();
Console.WriteLine("=== stan po operacjach ===");
Pokaz(magazyn);
Console.WriteLine();
Console.WriteLine("=== zestawienie po stanach ===");
foreach (Stan s in Enum.GetValues<Stan>())
{
int ile = 0;
foreach (Sprzet sp in magazyn)
{
if (sp.StanSprzetu == s)
{
ile++;
}
}
Console.WriteLine($"{s,-12} {ile} ({Komunikat(s)})");
}
Console.WriteLine();
Console.WriteLine("=== uprawnienia ===");
Dostep uczen = Dostep.Czytanie;
Dostep nauczyciel = Dostep.Czytanie | Dostep.Zapis | Dostep.Usuwanie;
Sprawdz("uczen", uczen);
Sprawdz("nauczyciel", nauczyciel);
Console.WriteLine();
Console.WriteLine("=== polki sa kopiowane, nie wspoldzielone ===");
Punkt polka = magazyn[0].Polka;
Punkt kopia = polka;
Console.WriteLine($"polka {polka}, kopia {kopia}, rowne: {polka.Equals(kopia)}");
static void Pokaz(List<Sprzet> lista)
{
foreach (Sprzet s in lista)
{
Console.WriteLine($"{s.Nazwa,-18} {s.KategoriaSprzetu,-13} polka {s.Polka,-8} {s.StanSprzetu}");
}
}
static string Komunikat(Stan s)
{
return s switch
{
Stan.Dostepny => "mozna wypozyczyc",
Stan.Wypozyczony => "u kogos w domu",
Stan.WNaprawie => "w serwisie",
Stan.Wycofany => "zdjete z inwentarza",
_ => "stan nieznany"
};
}
static void Sprawdz(string kto, Dostep d)
{
Console.WriteLine($"{kto,-11} {d,-30} zapis: {d.HasFlag(Dostep.Zapis)}");
}
enum Stan
{
Dostepny,
Wypozyczony,
WNaprawie,
Wycofany
}
enum Kategoria
{
Inne = 0,
Komputer = 1,
Prezentacja = 2,
Laboratorium = 3
}
[Flags]
enum Dostep
{
Brak = 0,
Czytanie = 1,
Zapis = 2,
Usuwanie = 4
}
struct Punkt
{
public int Rzad { get; }
public int Miejsce { get; }
public Punkt(int rzad, int miejsce)
{
Rzad = rzad;
Miejsce = miejsce;
}
public override string ToString()
{
return $"{Rzad}/{Miejsce}";
}
}
class Sprzet
{
public string Nazwa { get; }
public Kategoria KategoriaSprzetu { get; }
public Punkt Polka { get; }
public Stan StanSprzetu { get; private set; }
public Sprzet(string nazwa, Kategoria kategoria, Punkt polka)
{
Nazwa = nazwa;
KategoriaSprzetu = kategoria;
Polka = polka;
StanSprzetu = Stan.Dostepny;
}
public bool Wypozycz(string komu)
{
if (StanSprzetu != Stan.Dostepny)
{
Console.WriteLine($"{Nazwa}: odmowa, stan {StanSprzetu}");
return false;
}
StanSprzetu = Stan.Wypozyczony;
Console.WriteLine($"{Nazwa}: wypozyczono dla {komu}");
return true;
}
public void OddajDoNaprawy()
{
StanSprzetu = Stan.WNaprawie;
Console.WriteLine($"{Nazwa}: oddane do serwisu");
}
public void Wycofaj()
{
StanSprzetu = Stan.Wycofany;
Console.WriteLine($"{Nazwa}: wycofane z inwentarza");
}
}
=== stan magazynu ===
Laptop Dell Komputer polka 1/2 Dostepny
Projektor Epson Prezentacja polka 1/5 Dostepny
Drukarka HP Komputer polka 3/1 Dostepny
Mikroskop Laboratorium polka 4/4 Dostepny
=== operacje ===
Laptop Dell: wypozyczono dla Kasia Nowak
Laptop Dell: odmowa, stan Wypozyczony
Projektor Epson: oddane do serwisu
Mikroskop: wycofane z inwentarza
=== stan po operacjach ===
Laptop Dell Komputer polka 1/2 Wypozyczony
Projektor Epson Prezentacja polka 1/5 WNaprawie
Drukarka HP Komputer polka 3/1 Dostepny
Mikroskop Laboratorium polka 4/4 Wycofany
=== zestawienie po stanach ===
Dostepny 1 (mozna wypozyczyc)
Wypozyczony 1 (u kogos w domu)
WNaprawie 1 (w serwisie)
Wycofany 1 (zdjete z inwentarza)
=== uprawnienia ===
uczen Czytanie zapis: False
nauczyciel Czytanie, Zapis, Usuwanie zapis: True
=== polki sa kopiowane, nie wspoldzielone ===
polka 1/2, kopia 1/2, rowne: True
Co dzieje się po kolei
- Pole
StanSprzetumaprivate set, więc stan zmienia się wyłącznie metodami. Dzięki temu nikt z zewnątrz nie ustawi sprzętowi stanuWypozyczony, pomijając sprawdzenie. - Drugie wywołanie
Wypozyczna tym samym laptopie kończy się odmową, bo warunek porównuje wartości wyliczeniowe, a nie liczby — i czyta się jak zdanie. - Wypisanie
s.StanSprzetudaje nazwę stanu, bez żadnej tabeli tłumaczącej numery. Enum.GetValues<Stan>()zwraca wszystkie cztery pozycje, więc zestawienie obejmuje też stany, których nikt nie użył — przy liczeniu ręcznym łatwo takie pominąć.Kategoriama jawnie nadane numery i pozycjęInne = 0. Gdyby kategoria pochodziła z pliku, wartość nierozpoznana trafiłaby właśnie tam.Dostepz[Flags]trzyma kilka uprawnień w jednej zmiennej.ToString()wypisuje je po przecinku, aHasFlagsprawdza jedno z nich.Punktjest strukturą:Punkt kopia = polka;tworzy niezależną kopię, aEqualsporównuje pola, nie adresy — dlatego wynik jestTrue. Przy klasie dostałbyśFalse, o ile nie przesłoniłbyśEquals.
Sprawdź trzy rzeczy
- Dopisz do
enum StanpozycjęZgubionyi uruchom program bez zmiany metodyKomunikat. Zestawienie pokaże nowy stan z opisem „stan nieznany” — to zaleta gałęzi_. Potem dopisz gałąź i porównaj. - Zmień
struct Punktnaclass Punkt. Ostatni wiersz wypiszerowne: False, boEqualsklasy porównuje odwołania. Nic innego się nie zepsuje — na razie. - Dopisz
Console.WriteLine((Stan)7);. Zobaczysz7zamiast nazwy — to ta jedna dziura w szczelności typu wyliczeniowego.
Zestawienie elementów
| Element | Znaczenie | Uwagi |
|---|---|---|
enum Nazwa { A, B } | typ o zamkniętej liście wartości | Pozycje numerowane od 0. Kompilator odrzuca wartości spoza listy. |
A = 5 w enum | własny numer pozycji | Gdy liczby mają znaczenie poza programem. |
enum Nazwa : byte | mniejszy typ w tle | Domyślnie int. |
(int)wartosc | wyliczenie na liczbę | Zawsze się udaje. |
(Stan)liczba | liczba na wyliczenie | Nie sprawdza, czy taka pozycja istnieje. |
.ToString() | nazwa pozycji | Wywoływana też przez WriteLine i interpolację. |
Enum.GetValues<T>() | tablica wszystkich pozycji | Do pętli po stanach i do menu. |
Enum.TryParse<T> | rozpoznanie z tekstu | Drugi argument true ignoruje wielkość liter. |
Enum.IsDefined(w) | sprawdzenie, czy wartość jest na liście | Konieczne przy danych z zewnątrz. |
[Flags] | wyliczenie jako zestaw | Wartości: 1, 2, 4, 8… Składanie przez |. |
.HasFlag(x) | sprawdzenie jednej flagi | Zwraca bool. |
struct Nazwa { } | typ wartościowy | Przypisanie kopiuje zawartość. Bez dziedziczenia. |
default dla struktury | wszystkie pola zerowe | Struktura nie może być null. |
Punkt? | struktura, która może być pusta | Zapis z lekcji 34. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
Stan jako int z komentarzem | Komentarz jest w jednym miejscu, a porównania w dwudziestu. Po miesiącu nikt nie wie, co znaczy 2. |
Stan jako string | Literówka nie jest błędem kompilacji. "w naprawie" i "W naprawie" to różne wartości. |
| Brak pozycji o wartości 0 | Pole typu enum w nowym obiekcie i tak dostanie 0, a ta wartość nie będzie mieć nazwy. |
(Stan)99 bez sprawdzenia | Kompiluje się i działa. Zmienna ma wartość bez nazwy, a switch wpadnie do _. Użyj Enum.IsDefined. |
switch po enum bez gałęzi _ | CS8509 — kompilator ostrzega, że nie wszystkie wartości są obsłużone, i ma rację. |
Nowa pozycja w enum bez przejrzenia wszystkich switch | Program działa, ale w jednym miejscu zachowa się domyślnie. Szukaj po nazwie typu, gdzie jest używany. |
[Flags] z wartościami 1, 2, 3 | Zestaw 1 + 2 jest nieodróżnialny od pozycji 3. Muszą być potęgi dwójki. |
Enum.TryParse bez IsDefined | Tekst "7" zostanie przyjęty jako wartość liczbowa, choć nie ma takiej pozycji. |
struct z dziedziczeniem | CS0527. Struktura może implementować interfejsy, ale nie ma klasy bazowej. |
struct ustawiany na null | CS0037. Struktura zawsze ma wartość; jeśli potrzebujesz braku, napisz Punkt?. |
struct na coś dużego i zmiennego | Kopiowanie przy każdym przypisaniu i przekazaniu do metody. Do tego prowadzi do błędów opisanych w lekcji 50 — tam zobaczysz dokładnie jakich. |
Zadania
Napisz typ wyliczeniowy DzienTygodnia z siedmioma pozycjami od poniedziałku do niedzieli. W programie utwórz zmienną tego typu, przypisz jej środę i wypisz: samą wartość, jej numer po rzutowaniu na int oraz wynik porównania z sobotą. Następnie pętlą po Enum.GetValues<DzienTygodnia>() wypisz wszystkie dni z numerami. Na koniec spróbuj napisać DzienTygodnia d = 3; i zapisz w komentarzu numer błędu.
Napisz typ wyliczeniowy Ocena z pozycjami Niedostateczny = 1 do Celujacy = 6. Napisz klasę Sprawdzian z właściwościami Przedmiot i Wynik typu Ocena. Napisz metodę static string Slownie(Ocena o), która wyrażeniem switch zwraca komentarz do każdej oceny, z gałęzią _ na końcu. Utwórz tablicę pięciu sprawdzianów, wypisz je w kolumnach wraz z komentarzem i policz średnią ocen, rzutując wartości na int.
Do zadania 2 dopisz wczytywanie oceny od użytkownika. Program ma w pętli prosić o ocenę do momentu podania poprawnej: wykorzystaj Enum.TryParse<Ocena> z ignorowaniem wielkości liter, a po udanym rozpoznaniu dodatkowo sprawdź Enum.IsDefined. Sprawdź, co się dzieje przy wpisaniu celujacy, CELUJACY, 4, 9 oraz pustego wiersza, i zapisz wyniki wszystkich pięciu prób w komentarzu. Wyjaśnij, dlaczego samo TryParse nie wystarczyło.
Napisz typ wyliczeniowy Uprawnienia z atrybutem [Flags] i pozycjami Brak = 0, Czytanie, Zapis, Usuwanie, Administracja o właściwych wartościach. Napisz klasę Konto z właściwościami Login i Prawa oraz metodami Dodaj(Uprawnienia u), Odbierz(Uprawnienia u) i bool Moze(Uprawnienia u). W programie utwórz trzy konta: uczeń (tylko czytanie), nauczyciel (czytanie i zapis) i administrator (wszystko). Wypisz tabelkę z loginem, pełnym zestawem praw i czterema kolumnami tak/nie dla poszczególnych uprawnień. Potem odbierz nauczycielowi zapis, dodaj uczniowi usuwanie i wypisz tabelkę ponownie.
Napisz strukturę Wymiar z właściwościami tylko do odczytu Szerokosc i Wysokosc (typ double), konstruktorem, wyliczaną właściwością Pole oraz przesłoniętym ToString() w formacie 3,0 x 4,0. W programie utwórz wymiar, przypisz go do drugiej zmiennej i wypisz oba oraz wynik Equals. Utwórz też zmienną przez default i wypisz ją. Następnie spróbuj: napisać struct Wymiar : object, przypisać null do zmiennej typu Wymiar oraz zadeklarować Wymiar? i przypisać jej null. Zapisz w komentarzu, które z trzech prób się udały i jakie numery błędów dały pozostałe.
Napisz program do rezerwacji sal na konsultacje. Zadeklaruj: typ wyliczeniowy StanRezerwacji (Wolna, Zajeta, Odwolana), typ wyliczeniowy TypSali z jawnymi numerami (Zwykla = 1, Komputerowa = 2, Laboratorium = 3, Inna = 0), typ wyliczeniowy z [Flags] o nazwie Wyposazenie (Brak, Rzutnik, Tablica, Komputery, Naglosnienie) oraz strukturę Termin z właściwościami Dzien (typ DzienTygodnia, własny enum) i Godzina (typ int), z przesłoniętym ToString(). Napisz klasę Rezerwacja z właściwościami Sala, TypSali, Wyposazenie, Termin, StanRezerwacji (z private set) i Nauczyciel, oraz metodami bool Zarezerwuj(string kto) (udaje się tylko dla stanu Wolna) i void Odwolaj(). W programie utwórz listę co najmniej sześciu rezerwacji, wykonaj kilka udanych i nieudanych rezerwacji, a następnie wypisz: pełną tabelkę rezerwacji, zestawienie liczby rezerwacji w każdym stanie (pętlą po Enum.GetValues), listę sal mających jednocześnie rzutnik i nagłośnienie (sprawdzenie dwóch flag) oraz listę sal wolnych w poniedziałek. Na końcu, w komentarzu, odpowiedz: dlaczego Termin jest strukturą, a Rezerwacja klasą, oraz co by się zmieniło, gdyby było odwrotnie.
Co trzeba zapamiętać
enumto typ o zamkniętej liście wartości; kompilator odrzuca wszystko poza nią.- Za nazwami stoją liczby — domyślnie od 0, ale można je nadać samemu.
- Pierwsza pozycja (wartość 0) powinna mieć sens jako stan początkowy, bo taką wartość dostaje nowe pole.
- Rzutowanie liczby na
enumnie jest sprawdzane — dane z zewnątrz weryfikuj przezEnum.IsDefined. ToString()daje nazwę pozycji, dlatego wypisywanie stanów nie wymaga żadnej tabeli tłumaczeń.- W
switchpoenumzawsze zostawiaj gałąź_. Enum.GetValues<T>()daje wszystkie pozycje — do pętli, menu i zestawień.Enum.TryParserozpoznaje tekst i zwracabool, ale przyjmuje też zapis liczbowy.[Flags]plus wartości 1, 2, 4, 8… pozwala trzymać zestaw wartości w jednej zmiennej.structto typ wartościowy: przypisanie kopiuje zawartość, aEqualsporównuje pola.- Struktura nie dziedziczy, nie może być
nulli nie ma składowychvirtual. structwybieraj tylko dla małych, niezmiennych wartości; w razie wątpliwościclass.
Dokumentacja: Microsoft Learn — temat tej lekcji.