Blog Dla Programistów C#/.NET

środa, 12 listopada 2025

C# jako język ciągle się rozwija i nowe wersje wprowadzają usprawnienia, które upraszczają kod i czynią go bardziej zrozumiałym. W 2025 roku mamy do dyspozycji wiele nowoczesnych funkcji i składni, o których jeszcze kilka lat temu mogliśmy tylko pomarzyć. Warto je znać, aby pisać czytelny, zwięzły i idiomatyczny kod – zarówno w nowych projektach, jak i modernizując stare aplikacje. W tym artykule omawiam najciekawsze funkcje języka C#, które każdy współczesny Programista C#/.NET powinien znać.

Nowoczesny C# w 2025 - Funkcje, Które Musisz Znać

Top-level statements – prosty kod bez ceremonii


Jedną z rewolucyjnych zmian było wprowadzenie tzw. top-level statements (instrukcji najwyższego poziomu) w C# 9. Dzięki nim program główny nie wymaga już opakowania w klasę i metodę Main – możemy pisać kod bezpośrednio w pliku Program.cs. Oznacza to koniec z nadmiarowym szablonem aplikacji konsolowej. Wystarczy kilka linijek kodu w globalnym zakresie, a kompilator sam utworzy za nas odpowiednią klasę i punkt wejścia programu. Rezultat? Czytelniejszy kod i mniej zbędnego kodu "ceremonialnego". To podejście zostało również wykorzystane w minimalnych API (np. w ASP.NET Core), gdzie definicja całej aplikacji webowej mieści się w jednym pliku bez dodatkowego szablonu.


Globalne usingi i deklaracja przestrzeni nazw w jednym wierszu


Kolejne usprawnienia pojawiły się w C# 10, aby ograniczyć powtarzalny kod na początku plików. Pierwsze z nich to globalne dyrektywy using – możemy jednorazowo dodać global using Namespace; i sprawić, że przestrzeń nazw będzie dostępna we wszystkich plikach projektu. Dzięki temu nie musimy dodawać tych samych using w każdym pliku z osobna, co upraszcza utrzymanie zależności i porządku w projekcie.

Drugie usprawnienie to skrócona deklaracja przestrzeni nazw. Zamiast otwierać przestrzeń nazw klamrami na cały plik, w C# 10 możemy zadeklarować ją za pomocą średnika. Kod, który kiedyś wyglądał tak:

/* Przed C# 10: */
namespace MyApp
{
class Book { }
}

Teraz można zapisać zwięźlej:

/* C# 10 i nowsze: */
namespace MyApp;
class Book { }

Efekt jest ten sam, a zniknął jeden poziom wcięcia i para klamerek, przez co plik jest od razu czytelniejszy. W połączeniu z globalnymi using otrzymujemy czystszy, bardziej przejrzysty kod na starcie każdego pliku.


Dopasowywanie wzorców (Pattern Matching)


Pattern matching to funkcjonalność, która z każdą wersją C# staje się coraz potężniejsza. Pozwala ona na eleganckie sprawdzanie typu obiektu oraz jego właściwości w instrukcjach if czy switch – bez pisania zagnieżdżonych if-else lub rzutowań. Już C# 8 wprowadził wzorce właściwości, krotek czy pozycyjne, a C# 9 dodał m.in. wzorce relacyjne i logiczne (<, >, is not null, and, or, not). W praktyce oznacza to, że możemy w jednym wyrażeniu sprawdzić np. czy obiekt nie jest nullem i ma określone wartości pól.
Przykład: załóżmy, że chcemy sprawdzić czy obiekt emp jest ustawiony i czy pracuje w dziale "IT". Dawniej musieliśmy to zapisać z użyciem dwóch warunków:

if (emp != null && emp.Department == "IT") 
{
Console.WriteLine("Welcome");
}

W nowoczesnym C# zapiszemy to zgrabniej przy pomocy pattern matching:

if (emp is { Department: "IT" }) 
{
Console.WriteLine("Welcome");
}

Wzorzec { Department: "IT" } sprawdza jednocześnie, czy emp nie jest null oraz czy jego właściwość Department ma wartość "IT". Możemy iść dalej – dopasowanie wzorca pozwala też od razu wyciągnąć wartości do zmiennych. Rozszerzmy przykład, aby od razu pobrać nazwisko pracownika:

if (emp is Employee { Department: "IT", Name: var n }) 
{
Console.WriteLine($"Welcome {n}");
}

W powyższym kodzie warunek sprawdza, czy emp jest obiektem Employee z działu IT, a jeśli tak, to przypisuje jego Name do zmiennej n, którą możemy wykorzystać wewnątrz bloku. Wszystko to w jednej, czytelnej konstrukcji zamiast kilku zagnieżdżonych instrukcji.

Warto dodać, że najnowsze wersje C# wprowadziły także wzorce list (C# 11) pozwalające dopasowywać sekwencje i kolekcje (np. sprawdzić strukturę tablicy lub listy za pomocą składni [ ]). Dopasowywanie wzorców upraszcza kod warunkowy i pozwala wyrazić złożone logiki sprawdzania danych w deklaratywny, zwięzły sposób.


Rekordy i skrócona definicja obiektów


Record to nowy typ referencyjny wprowadzony w C# 9, który ułatwia tworzenie niezmiennych obiektów służących do przechowywania danych. Używając słowa kluczowego record, tworzymy klasę, która domyślnie jest niemutowalna i otrzymuje wbudowane wsparcie dla takich rzeczy jak porównywanie wartości (value equality) czy czytelne formatowanie wyjściowe. Innymi słowy, rekordy to zwięzła składnia na klasę z automatycznie zaimplementowanymi metodami pomocniczymi do enkapsulacji danych.

Najprostszy przykład rekordu:

public record Person(string FirstName, string LastName);

W jednej linii zdefiniowaliśmy klasę Person z dwoma właściwościami tylko do odczytu (init-only). Kompilator wygeneruje za nas konstruktor, właściwości, a także metody Equals, GetHashCode i ToString odpowiednio porównujące i wyświetlające zawartość obiektu. Dzięki temu taki rekord możemy traktować jak wartość: dwie instancje Person("Jan", "Kowalski") będą sobie równe wartościowo (== zwróci true), co w przypadku zwykłych klas domyślnie nie ma miejsca bez nadpisania metod.

Rekordy idealnie nadają się do przechowywania danych konfiguracyjnych, wyników, DTO itp., gdzie chcemy prostego typu z właściwościami i bez zbędnej logiki. Dla bardziej złożonych przypadków można definiować rekordy w rozszerzonej formie (np. z ciałem klasy, dziedziczeniem lub mutowalnymi polami, jeśli naprawdę trzeba).

W kontekście rekordów pojawiło się też słowo kluczowe init dla setterów właściwości. Pozwala ono ustawiać wartość tylko podczas inicjalizacji obiektu (w konstruktorze lub przy użyciu składni object initializer), dzięki czemu po utworzeniu obiektu właściwość staje się tylko do odczytu. Przykładowo:

public record Book
{
public string Title { get; init; }
public string Author { get; init; }
}

Tutaj Title i Author można ustawić tylko raz przy tworzeniu Book, potem są one niemodyfikowalne – co wspiera niezmienność obiektów.
C# 12 idzie o krok dalej i udostępnia tzw. primary constructors również dla zwykłych klas i struktur (wcześniej taka składnia z parametrami przy nazwie typu dostępna była tylko w rekordach). Teraz możemy definiować konstruktory podstawowe bezpośrednio w deklaracji klasy. Na przykład:

public class Point(int x, int y)
{
public int X => x;
public int Y => y;
}

Taki zapis spowoduje, że kompilator wygeneruje konstruktor przyjmujący (int x, int y), a parametry te są dostępne wewnątrz ciała klasy (jak pokazano powyżej przy definicji właściwości). Oszczędzamy kilka dodatkowych linii kodu, które normalnie przeznaczylibyśmy na pisanie konstruktora i pól prywatnych. Warto pamiętać, że gdy klasa ma primary constructor, kompilator nie dodaje już domyślnego konstruktora bezparametrowego – mamy więc pełną kontrolę nad sposobem tworzenia obiektu.

Podsumowując, rekordy i konstruktory w definicji klasy to ukłon w stronę programistów ceniących zwięzłość. Pozwalają szybciej definiować modele danych i proste klasy, skupiając się na tym co dana klasa przechowuje, zamiast na ceremonialnym kodzie jak to przechować.


Bezpieczniejsze operacje na null i wymagane właściwości


Każdy programista C# zna klasyczny wyjątek NullReferenceException. Aby zredukować ryzyko jego wystąpienia, w C# 8 wprowadzono nullable reference types – czyli możliwość oznaczania typów referencyjnych adnotacją ? jako dopuszczających null. Po włączeniu tej funkcji kompilator ostrzega nas, gdy np. próbujemy użyć nieinicjalizowanej zmiennej, która może być null, lub kiedy zapominamy sprawdzić wartość zwracaną oznaczoną jako mogącą być null. W praktyce system ten działa jak wczesne ostrzeżenie przed potencjalnym NullReferenceException już na etapie kompilacji, co znacznie podnosi bezpieczeństwo kodu. Jeśli typ jest oznaczony np. jako string?, to wiemy, że musimy liczyć się z null i obsłużyć ten przypadek, natomiast zwykły string jest traktowany jako nie-nullowalny (i przypisanie mu null spowoduje ostrzeżenie). Ta zmiana wymagała dostosowania wielu bibliotek, ale obecnie (2025) jest standardem w nowych projektach – zaleca się włączać obsługę nullowalności dla lepszej jakości kodu.

Drugim mechanizmem związanym z bezpiecznym inicjowaniem obiektów są wymagane właściwości (słowo kluczowe required). Pojawiło się ono w C# 11 i pozwala oznaczyć właściwości, które muszą zostać ustawione podczas tworzenia obiektu. W połączeniu z inicjatorami obiektów (object initializer) kompilator wymusi, byśmy podali wartości dla wszystkich wymaganych pól, inaczej kod się nie skompiluje. Przykład:

public class User 
{
public required string Name { get; init; }
public required string Email { get; init; }
}

var u = new User { Name = "Ala" }; /* Błąd kompilacji – brakuje Email */

Tutaj klasa User oznacza, że Name i Email są obowiązkowe. Przy próbie utworzenia obiektu bez podania tych właściwości dostaniemy błąd jeszcze przed uruchomieniem programu. Dzięki temu unikamy sytuacji, w której obiekt jest niekompletnie zainicjowany, co często prowadziło do błędów w czasie działania aplikacji. Mechanizm required świetnie uzupełnia się z ideą typów nullowalnych – razem zapewniają, że nasze obiekty mają wszystkie potrzebne dane i że raczej nie trafimy na niespodziewane null tam, gdzie ich być nie powinno.

Warto przy okazji wspomnieć, że już wcześniej C# ułatwił radzenie sobie z null poprzez operator bezpiecznego dostępu ?. i operator łączenia z wartością domyślną ??. Pozwalają one zwięźle obsłużyć przypadki, gdy coś może być null (np. var length = str?.Length ?? 0; da długość stringa lub 0, gdy str jest null). Te operatory to dziś standardowy element składni, lecz to właśnie nullable reference types i required przenoszą bezpieczeństwo o poziom wyżej – na etap kompilacji.


Surowe literały tekstowe (raw string literals)


Praca z łańcuchami znaków również stała się wygodniejsza. Surowe literały string wprowadzone w C# 11 pozwalają definiować ciągi tekstowe z zachowaniem oryginalnego formatowania, bez uciekania znaków specjalnych. Taki literał rozpoczyna się od potrójnego cudzysłowu """ i kończy się potrójnym cudzysłowem. W środku możemy umieścić dowolny tekst: cudzysłowy, backslashe, znaki nowej linii – wszystko będzie traktowane dosłownie, a nie jako sekwencje sterujące.

Przykładowo, jeśli chcemy zdefiniować tekst w formacie JSON albo zapytanie SQL w naszym kodzie, możemy teraz zrobić to dużo czytelniej:

string query = """
SELECT *
FROM Users
WHERE Name = "Jan Kowalski"
AND IsActive = true;
""";

Dzięki użyciu """ cały ten tekst (wraz z znakami nowej linii i cudzysłowami wewnątrz) został zapisany dokładnie tak, jak chcemy, bez żadnych dodatkowych znaczków typu \n czy \". Surowe literały znakowe znakomicie sprawdzają się przy wstawianiu większych bloków tekstu w kodzie – np. przy budowaniu ciągów JSON, wyrażeń regularnych czy komunikatów, które wcześniej przez nadmiar znaków escape były trudne do czytania.

Co więcej, surowe literały współpracują z interpolacją stringów. Jeśli potrzebujemy użyć składni ${...} wewnątrz takiego literału, możemy to zrobić zaczynając ciąg od dolarów i odpowiedniej liczby cudzysłowów, np. $""" (wtedy wewnątrz pojedyncze { i } są traktowane dosłownie, a {{ }} oznaczają faktyczne nawiasy dla interpolacji). To nieco bardziej zaawansowane, ale ważne, że projektanci języka pomyśleli i o tym przypadku.

Podsumowując, raw string literals to mała zmiana, która ogromnie poprawia wygodę pracy z tekstem w kodzie. Koniec z chaotycznymi ciągami pełnymi ukośników – teraz widzimy w kodzie od razu docelowy tekst w czytelnej formie.


Zakończenie


Powyższe przykłady to tylko część nowoczesnych funkcji C#, które ułatwiają życie programistom. Język rozwija się szybko – praktycznie co roku dostajemy usprawnienia zwiększające produktywność, czytelność i bezpieczeństwo kodu. Warto być na bieżąco z nowościami, bo wiele z nich na dobre zmienia styl pisania kodu w C#. Mniej szablonowego kodu, więcej wyrazu zamiarów programisty – taki jest kierunek rozwoju języka.

Jeśli któryś z tematów wydaje Ci się skomplikowany lub chcesz nauczyć się C# od podstaw w praktyczny sposób – zapraszam do mojego kursu "Zostań Programistą .NET". Krok po kroku tłumaczę tam podstawy C#, a także pokazuję, jak korzystać z omawianych wyżej nowoczesnych funkcji w codziennym programowaniu. Dzięki temu w 3 miesiące przejdziesz drogę od zupełnego zera do poziomu młodszego programisty C#/.NET, ucząc się pisania nowoczesnego i profesjonalnego kodu.

Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
CTO & Founder - FindSolution.pl
Programista C#/.NET. Specjalizuje się w Blazor, ASP.NET Core, ASP.NET MVC, ASP.NET Web API, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Dodaj komentarz

Wyszukiwarka

© Copyright 2025 modestprogrammer.pl | Sztuczna Inteligencja | Regulamin | Polityka prywatności. Design by Kazimierz Szpin. Wszelkie prawa zastrzeżone.