Nauka programowania w C# to świetna przygoda, ale każdy początkujący prędzej czy później natrafi na typowe "miny". Błędy są nieodłączną częścią nauki – warto podejść do nich z uśmiechem i wyciągać wnioski, zamiast się zrażać. W tym artykule omawiam pięć klasycznych wpadek, które popełnia niemal każdy nowy programista C# (mnie też się zdarzały). Każdy błąd zilustruję krótkim przykładem kodu i jego skutkiem wraz z komentarzem. Niech te małe katastrofy staną się dla nas cenną lekcją :)
1. NullReferenceException – obiekt, którego nie ma
NullReferenceException to chyba najbardziej klasyczny wyjątek, na jaki natrafiają początkujący. Pojawia się, gdy próbujemy odwołać się do obiektu, który... nie istnieje (czyli ma wartość null). To tak, jakby próbować zapalić lampkę, której nie podłączyliśmy do prądu – nic z tego nie wyjdzie, a program się wykrzaczy. Dla zobrazowania, spójrz na poniższy kod:
string tekst = null;
Console.WriteLine(tekst.Length);W efekcie dostaniemy błąd: System.NullReferenceException: Object reference not set to an instance of an object.
Mówiąc krótko, próbowaliśmy użyć zmiennej tekst, ale nie wskazuje ona na żaden obiekt (jest null). Aby uniknąć tego problemu, upewnij się, że inicjalizujesz obiekty przed użyciem lub sprawdzasz, czy nie są null (np. if (tekst != null) {...}). W nowoczesnym C# istnieje mechanizm nullable reference types, dzięki któremu kompilator ostrzega przed ryzykiem natrafienia na null – warto zwracać uwagę na te komunikaty.
Fun fact: NullReferenceException bywa dla wielu z nas "chrztem bojowym" w .NET – każdy go kiedyś zaliczy, ważne by wyciągnąć z niego naukę!
2. '=' vs '==' – pomylenie przypisania z porównaniem
Kolejna klasyczna wpadka to użycie pojedynczego = zamiast podwójnego == w warunku if. Wielu początkujących (zwłaszcza po doświadczeniach z innymi językami lub przez zwykłe roztargnienie) napisze kiedyś w if jedno = i będzie się głowić, czemu program zawsze wchodzi do tego if. W C# kompilator na szczęście wyłapuje większość takich pomyłek – nie pozwoli np. napisać if(x = 5) (bo wynik przypisania nie jest typu bool). Jednak jeśli nasza zmienna jest typu bool, to if (isValid = true) skompiluje się i... zawsze zadziała. Zamiast porównania ustawiliśmy wartość, więc warunek nigdy nie będzie fałszywy. Przykład:
bool isValid = false;
if (isValid = true)
{
Console.WriteLine("Dane są poprawne!");
}W powyższym kodzie chcieliśmy sprawdzić, czy flaga isValid ma wartość true, ale przez pomyłkę nadaliśmy jej wartość true zamiast porównać. Efekt? Warunek zawsze jest spełniony, więc zobaczymy komunikat "Dane są poprawne!" niezależnie od początkowej wartości isValid. Taka pomyłka prowadzi do trudnych do wykrycia błędów logicznych. Zapamiętaj: do porównywania używamy == (podwójne równa się), a pojedyncze = służy wyłącznie do przypisywania wartości.
3. Niezamknięte połączenie z bazą danych
Pracując z bazą danych (albo np. z plikami), początkujący często skupiają się na tym, żeby w ogóle zadziałało, i zapominają o posprzątaniu po sobie. Klasyczny przykład: otwieramy połączenie z bazą danych i zapominamy je zamknąć. Kod może wyglądać tak:
var connection = new SqlConnection(connectionString);
connection.Open();
// ... wykonujemy operacje na bazie ...
// zapomnieliśmy o:
// connection.Close();Po zakończeniu działania takiego kodu połączenie z bazą nadal pozostaje otwarte. Jeśli zrobimy tak wiele razy, możemy szybko przekroczyć limit dostępnych połączeń do serwera bazy albo po prostu będziemy marnować zasoby. To trochę jak zostawienie odkręconej wody – aplikacja niby działa, ale cenne zasoby uciekają.
Rozwiązanie? Zawsze zamykaj połączenia (najlepiej w konstrukcji using, która automatycznie wywoła Dispose()/Close() za Ciebie, nawet gdy wystąpi wyjątek). Ta zasada dotyczy też innych zasobów jak pliki, strumienie itp. – jeśli coś otwierasz, pamiętaj, by to zamknąć. Inaczej prędzej czy później system upomni się o "rachunek" za niewypuszczone zasoby, zwykle w najmniej odpowiednim momencie.
4. IndexOutOfRangeException – wyjście poza zakres tablicy
Kolejny typowy błąd to próba odczytu lub zapisu poza granicami tablicy (lub listy). W C# indeksy tablic zaczynają się od 0, więc jeśli masz 5-elementową tablicę, dostępne indeksy to 0..4. Łatwo się pomylić przy obliczaniu indeksów lub iteracji w pętli i spróbować dostać element, który nie istnieje. W takiej sytuacji program zareaguje wyjątkiem IndexOutOfRangeException z komunikatem w stylu "Index was outside the bounds of the array". Klasyczny przykład:
int[] liczby = { 1, 2, 3 };
Console.WriteLine(liczby[3]);Powyżej tablica liczby ma rozmiar 3, ale ostatni dostępny indeks to 2. Próba odczytu elementu o indeksie 3 zakończy się błędem IndexOutOfRangeException. To jak próba wyjęcia czwartego cukierka z pudełka, w którym są tylko trzy – program zgłosi sprzeciw, bo taki element nie istnieje. Aby uniknąć takich niespodzianek, pamiętaj, że dla tablicy o n elementach indeksy wahają się od 0 do n-1. Uważaj też na pętle – klasyczny bug to iteracja for (int i = 0; i <= n; i++) zamiast i < n. Jeden znak różnicy, a efekt dramatyczny.
5. Uwaga na if – zgubiony średnik i brak klamr
Na koniec pułapka składni, która potrafi sprawić, że program działa nie tak, jak myślisz, mimo że się kompiluje. Jeśli po warunku if przypadkiem postawisz średnik (;), C# uzna, że blok if jest pusty, a kolejny blok kodu wykona się zawsze. Podobnie, jeśli zapomnisz nawiasów klamrowych przy if i dodasz kolejną linię kodu, to tylko pierwsza linia będzie objęta warunkiem, a reszta wykona się niezależnie. Te błędy nie wywołują wyjątków, ale potrafią napsuć krwi, bo program niby działa, tylko logika się "rozjeżdża". Spójrzmy na przykład ze zbędnym średnikiem:
int x = 5;
if (x == 0);
{
Console.WriteLine("x jest równe zero!");
}Przez ten niefortunny średnik kod w klamrach wykona się zawsze, nawet gdy x wcale nie jest 0. Wygląda niewinnie, a potrafi zabrać sporo czasu na znalezienie przyczyny takiego zachowania. Podobnie w przypadku braku klamer:
int y = 0;
if (y < 0)
Console.WriteLine("y jest ujemne");
Console.WriteLine("Koniec");Tutaj druga instrukcja Console.WriteLine("Koniec"); wykona się zawsze, bo nie jest objęta klamrami if. Dlatego zawsze stawiaj klamry, nawet przy jednej linijce wewnątrz if – dzięki temu unikniesz przypadkowego dodania kolejnej linii poza warunkiem. A przed każdym if dobrze zerknąć, czy nie ma za nim zbłąkanego ;. Taki zbędny średnik to prawdziwy sabotażysta w kodzie – niewidocznie psuje logikę.
Podsumowanie
Na starcie przygody z C# z pewnością zaliczysz kilka z powyższych wpadek – i nie ma w tym nic złego! Ważne, by wyciągać z nich wnioski i z każdym błędem poszerzać swoją wiedzę.
Jeśli czujesz, że przydałoby Ci się solidne, uporządkowane wprowadzenie do C#/.NET (z naciskiem na dobre praktyki i unikanie powyższych pułapek), zachęcam do sprawdzenia moich kursów online. Krok po kroku przeprowadzę Cię tam przez podstawy programowania w C#, dzieląc się najlepszymi praktykami i doświadczeniem – tak, by nauka była efektywna i pozwoliła bezboleśnie ominąć podobne wpadki. Powodzenia w kodowaniu – pamiętaj, że nie myli się tylko ten, kto nic nie robi.
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.