Co ciekawego w .NET 11?

7 lipca 2026Kamil Kiełbasa
hero

Dopiero co przez całą Polskę przewaliła się najgorsza fala upałów w historii pomiarów, termometr momentami pokazywał ponad 39 stopni, a jedyne o czym dało się myśleć, to jak dotrwać do zmroku. Na szczęście już po wszystkim, powietrze odpuściło i głowa wróciła do roboty. A skoro wróciła, to usiadłem, mój drogi czytelniku, do czegoś, co chodziło za mną od dłuższego czasu: przeglądu tego, co szykuje się w .NET 11. Premiera wypada dopiero w listopadzie, ale mamy już za sobą pięć wersji preview, więc obraz jest całkiem wyraźny. Na tyle wyraźny, że warto zawczasu wiedzieć, na co się piszemy.

I powiem Ci szczerze: to wydanie jest... spokojne. Nie ma tu jednego wielkiego feature-a, który wywróci Twój warsztat do góry nogami. Jest za to garść zmian, które po cichu robią swoją robotę. Jedne obniżą Ci rachunek za chmurę, choć palcem nie kiwniesz. Inne uproszczą kawałek kodu. A jeszcze inne wymuszą decyzję o migracji, i to właśnie na nie architekt powinien patrzeć najuważniej.

Zebrałem tutaj to, co z całej tej listy realnie dotyka nas, ludzi budujących systemy webowe.

Zanim ruszymy, mała gwiazdka: opieram się na wersji preview (stan na .NET 11 Preview 5, lipiec 2026). Do premiery w listopadzie część API i domyślnych ustawień jeszcze może się przemeblować. Traktuj ten tekst jak mapę kierunku, a nie jak dokumentację produkcyjną, i przed wdrożeniem zerknij do aktualnych release notes.

Runtime Async, czyli async/await wraca do fabryki

Zacznijmy od zmiany, która wygląda na wewnętrzną sprawę runtime'u, a tak naprawdę dotyka każdego, kto kiedykolwiek napisał await. Czyli nas wszystkich.

Cofnijmy się na moment. Od C# 5 async/await stoi na sprytnej sztuczce kompilatora. Roslyn bierze każdą Twoją metodę asynchroniczną i przepisuje ją na maszynę stanów, czyli osobną, wygenerowaną klasę, która przenosi zmienne lokalne do pól i pilnuje, w którym miejscu wznowić robotę po kolejnym await. Działa świetnie, ale nie za darmo: płacimy za to alokacjami i tymi słynnymi, nieczytelnymi stack trace'ami, w których prawdziwej ścieżki wywołania trzeba się domyślać jak w kryminale.

W .NET 11 cały ten ciężar przenosi się z kompilatora do runtime'u (Microsoft opisuje to w nowościach runtime'u). Kompilator emituje teraz dużo prostsze IL, oznaczone atrybutem [MethodImpl(MethodImplOptions.Async)], a całą magię zawieszania i wznawiania metody bierze na siebie sam runtime:

[MethodImpl(MethodImplOptions.Async)]
public async Task<int> GetUserAgeAsync(int id)
{
    var user = await _repository.FindAsync(id);
    return user.Age;
}

Co Ci z tego przyjdzie? Trzy rzeczy. Po pierwsze, stack trace'y robią się LUDZKIE, bo runtime widzi wywołania asynchroniczne jak normalny stos, więc diagnostyka produkcyjnego pożaru przestaje być wróżeniem z fusów. Po drugie, alokacji jest mniej, bo zmienne lokalne zostają na stosie i lądują na stercie tylko wtedy, gdy naprawdę przeżywają await. Przy tysiącach równoległych requestów Twój GC wyraźnie odetchnie. Po trzecie, całość działa wreszcie z Native AOT i ReadyToRun, czego wcześniejsze podejścia nie miały.

Domyślnie feature siedzi już włączony w bibliotekach shared framework. A gdybyś z jakiegoś powodu chciał go u siebie ubić, załatwia to jedna linijka w pliku projektu:

<UseRuntimeAsync>false</UseRuntimeAsync>

I tu najlepsze: nie przepisujesz ani znaku. Ten sam kod po prostu zaczyna kosztować mniej, a efekt zobaczysz nie w źródłach, tylko na wykresach alokacji i czasu odpowiedzi pod obciążeniem.

Jedno mnie w tym wszystkim uwiera. To świeży feature, w dodatku przepisany kawał fundamentu platformy, więc gdzieś z tyłu głowy zostaje mi obawa o choroby wieku dziecięcego. Happy path na pewno jest przeklikany na wylot, o niego się nie boję. Boję się tych rzadkich, brzydkich edge case-ów, w których nowa maszyneria mogłaby zachować się inaczej niż stara, podłamać stabilność albo wprowadzić własne, nowe dziwactwa, których wcześniej po prostu nie było. Dlatego zanim wpuszczę to na produkcję pod pełnym obciążeniem, dam temu chwilę dojrzeć i sam solidnie przetestuję.

Wydajność w prezencie (i jeden haczyk)

Motywem przewodnim całego preview jest wydajność, w dużej mierze taka, o którą sam nie musisz nawet zabiegać. JIT dostał kolejną porcję optymalizacji: wycina zbędne sprawdzenia zakresu tablic, zwija stałe w SequenceEqual, upraszcza wyrażenia switch, wyrzuca martwe gałęzie. ReadyToRun generuje wyspecjalizowane helpery, a dla operacji na kolekcjach z domyślnym komparatorem Microsoft chwali się przyspieszeniem rzędu 20× (benchmarki i szczegóły znajdziesz w nowościach runtime'u). Do tego dorzucono usprawnienia GC w trimmingu i kompakcji pamięci. Ładnie.

Większość tego dostajesz gratis, samą zmianą target framework. Ale jest jeden haczyk, na który architekt powinien nadstawić ucha: podniesiono minimalne wymagania co do instrukcji procesora na x86/x64 i Arm64 (wspomina o tym przegląd .NET 11). Runtime zakłada teraz nowsze zestawy instrukcji, dzięki czemu utrzymuje mniej ścieżek zgodności i generuje lepszy kod.

Na współczesnym sprzęcie? Nawet tego nie zauważysz. Ale jeśli deployujesz na wiekowe serwery bare-metal, egzotyczne instancje w chmurze albo mocno okrojone emulatory w CI, to zajrzyj tutaj ZANIM zrobisz to na produkcji. Bo to dokładnie ten typ niespodzianki, który lubi wyskoczyć w najmniej odpowiednim momencie.

C# 15 — mniej ceremonii w modelowaniu domeny

Razem z .NET 11 wjeżdża C# 15. Największa nowość języka to unie, ale te sobie tutaj odpuszczę, bo poświęciłem im cały osobny artykuł i nie ma co się powtarzać. Zostają trzy rzeczy, które realnie zmieniają to, jak piszesz kod na co dzień.

Pierwsza to zamknięte hierarchie. Klasę bazową znaczysz modyfikatorem closed i mówisz kompilatorowi wprost: dziedziczyć wolno TYLKO w tym assembly, a lista wariantów jest kompletna. Efekt? switch potrafi być wyczerpujący bez gałęzi default:

public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

string Describe(GateState state) => state switch
{
    Closed      => "zamknięta",
    Open(var p) => $"otwarta w {p}%",
    // Brak ostrzeżenia — kompilator wie, że każdy wariant jest obsłużony.
};

Wcześniej kompletność takiego switcha wisiała wyłącznie na Twojej dyscyplinie. Teraz pilnuje jej kompilator, a to akurat ta różnica, za którą pokocha go każdy, kto modeluje domenę skończonymi stanami.

Jedna uwaga z pola walki, bo odpaliłem to sobie na żywo na preview. W .NET 11 Preview 5 runtime nie dostarcza jeszcze atrybutu System.Runtime.CompilerServices.ClosedAttribute, a bez niego projekt się nie zbuduje. Zanim domknięcie trafi na stałe, musisz zadeklarować go sobie sam, dosłownie kilkoma linijkami:

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class ClosedAttribute : Attribute { }

Ten sam rodzaj świeżynki co przy uniach, gdzie UnionAttribute też dojechał dopiero w Preview 5.

Druga to argumenty w collection expressions. Na początku wyrażenia możesz teraz wstawić with(...) i przekazać kolekcji dodatkowe ustawienia, na które w tym skróconym zapisie wcześniej nie było miejsca. Możesz na przykład z góry zarezerwować liście trochę miejsca, żeby nie musiała się w kółko powiększać przy dodawaniu elementów, albo kazać zbiorowi traktować „Hello" i „HELLO" jak to samo słowo:

List<string> names = [with(capacity: values.Length * 2), .. values];
HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
// set ma jeden element, bo wielkość liter nie jest tu brana pod uwagę.

Drobiazg, ale wcześniej w takiej sytuacji trzeba było porzucić zwięzłe [..] i wrócić do rozwlekłego new. Teraz już nie.

Trzecia to przebudowa modelu memory safety. C# 15 zaczyna rozłożoną na lata operację, w której unsafe przestaje dotyczyć samego istnienia wskaźników, a zaczyna dotyczyć operacji, które NAPRAWDĘ sięgają do niezarządzanej pamięci. W pierwszym kroku deklaracja wskaźnika, operator &, fixed, stackalloc i sizeof nie potrzebują już unsafe. Wymaga go dopiero dereferencja i dostęp przez wskaźnik. Jeśli klepiesz zwykłe API webowe, to dla Ciebie ciekawostka. Ale jeśli grzebiesz w interop albo bibliotekach pisanych pod wydajność, warto obserwować ten kierunek, bo audyt bezpieczeństwa pamięci staje się dzięki temu dużo bardziej chirurgiczny.

ASP.NET Core, czyli konkrety dla web-developera

Tu wchodzimy na teren, po którym web-developer chodzi codziennie. Wszystkie zmiany zebrano w release notes ASP.NET Core 11, a ja wyłuskam trzy, które najbardziej rzucają się w oczy.

Zacznijmy od kompresji Zstandard. Wjechała do frameworka jako wbudowana kompresja odpowiedzi i dekompresja żądań, w dodatku domyślnie włączona. Zstd ma naprawdę ładny stosunek stopnia kompresji do zjadanego CPU, więc dla API, które sypie JSON-em na potęgę, oznacza mniejszy transfer małym kosztem. Tylko zanim otworzysz szampana, upewnij się, że Twój reverse proxy albo CDN już czegoś nie kompresuje. Bo kompresować dwa razy to jak zakładać dwa swetry naraz: cieplej się nie zrobi, a ruszać się trudniej.

Idziemy dalej, do Kestrela. Zaczyna on teraz przetwarzać żądania HTTP/3, nie czekając grzecznie na control stream i ramkę SETTINGS. W praktyce to niższe opóźnienie pierwszego żądania na świeżym połączeniu. Pojedynczo drobiazg, ale przy dużym ruchu i krótkich sesjach te milisekundy zbierają się w coś, co widać na wykresie.

I na koniec OpenAPI. Generator ogarnia wreszcie odpowiedzi binarne, czyli FileContentResult mapuje się na schemat type: string, format: binary. Ciekawszy jest jednak drugi dodatek, czyli rozpoznawanie nowej metody HTTP QUERY. Znasz pewnie ten ból. Masz endpoint do wyszukiwania, warunków filtrowania przybywa, aż w końcu robi się ich tyle, że przestają mieścić się w adresie URL. No i co wtedy? Przerzucaliśmy się z GET na POST, żeby upchnąć wszystkie kryteria w ciele żądania. Działało, ale było mylące, bo POST kojarzy się ze zmianą stanu, a nie z niewinnym szukaniem. No i mało kto nazwie POST-a idempotentnym.

QUERY załatwia dokładnie ten zgrzyt. To nowa metoda HTTP prosto ze specyfikacji, bezpieczna i idempotentna jak GET, ale z prawem do wysłania ciała żądania. Czyli mówisz wprost: „nic tu nie zmieniam, tylko szukam, po prostu warunków mam za dużo, żeby zmieściły się w URL-u". A że ASP.NET Core opisuje ją teraz w OpenAPI, wygenerowani klienci i dokumentacja będą bliżsi temu, co Twoje API naprawdę robi, a nie temu, co mu się wydaje.

Ekosystem: EF Core, System.Text.Json i bezpieczniejszy build

Poza wielkimi tematami uzbierała się garść mniejszych smaczków z bibliotek i tooling-u. Lecę po nich szybko:

  • System.Text.Json serializuje wreszcie IAsyncEnumerable<T> jako JSON Lines (flaga topLevelValues: true), czyli rekord po rekordzie w osobnych liniach. Złoto przy logach, feedach i eksportach, gdzie nie chcesz trzymać w pamięci całej tablicy naraz.
  • LINQ dostał pełny outer join jako wbudowaną operację. Koniec ze sklejaniem go na piechotę z dwóch left joinów.
  • EF Core wprowadza diagnostykę EF1004, która krzyczy, gdy odpalasz zapytanie synchronicznie, choć powinno lecieć async (What's New in EF Core 11). To dokładnie ten cichy grzeszek, który latami blokuje wątki pod obciążeniem. Zmienia się przy okazji domyślny poziom zgodności na SQL Server 2022, co dla starszych baz bywa zmianą łamiącą, więc miej to na oku.
  • Kryptografia dostała natywne uzgadnianie kluczy X25519.
  • A SDK potrafi teraz w trakcie builda sprawdzić, czy Twoje zależności nie mają znanych podatności albo nie wypadły już ze wsparcia (audyt pakietów NuGet, nowości SDK). Czyli bezpieczeństwo łańcucha dostaw przesunięte tam, gdzie reaguje się najtaniej. Jeśli odpowiadasz w firmie za compliance, akurat ten punkt podkreśl sobie na czerwono.

Podsumowanie, czyli w co grać?

Gdybym miał zamknąć to jednym zdaniem: najwięcej z .NET 11 dostajesz za darmo. Runtime async, tańszy GC czy audyt zależności w buildzie przyjdą do Ciebie same przy najbliższej aktualizacji i nie policzą sobie ani jednej dodatkowej linijki kodu. A jeśli zechcesz sięgnąć głębiej, closed hierarchies i argumenty w collection expressions czekają na Ciebie w C# 15.

Ja tam .NET 11 przywitam z otwartymi ramionami, tylko bez fajerwerków. To solidna, spokojna cegła w murze, a nie rewolucja. I bardzo dobrze.

Mam nadzieję, że artykuł się podobał. Jak zwykle do następnego!

Cześć!

Referencje

By Bd90 | 07-07-2026 | .NET
Czytaj dalej

Powiązane wpisy

Czy na takie Unie w C# czekaliśmy?
Powtórka z Algorytmiki: Binary Search
Integracja Aplikacji .NET z Zitadel

/ komentarze załadują się, gdy do nich dotrzesz…