Branża ITCIOProgramowaniePolecane tematy
Jakie zmiany Microsoft wprowadza w nowej wersji środowiska programistycznego .NET 6?
Końcówka 2021 roku to czas nie tylko powrotu świątecznych przebojów do centrów handlowych, ale także okres istotnych zmian dla wielu programistów związanych ze środowiskiem .NET. Z początkiem listopada br. Microsoft zdecydował się na opublikowanie ostatecznej wersji sztandarowego środowiska programistycznego .NET 6. Pomimo krótkiego odstępu czasu od premiery poprzedniej wersji platformy, najnowsze wydanie wprowadza szereg zmian istotnych z punktu widzenia deweloperów i zespołów utrzymaniowych.
Po pierwsze, Microsoft daje 6 miesięcy na przejście do najnowszej wersji środowiska .NET 6, zaznaczając przy tym, że adaptacja z .NET 5 powinna być jedną z najszybszych w historii dotychczasowych edycji Software Development Kit – SDK. W zamian oferuje wsparcie techniczne aż do roku 2024, tym samym dając dużą dozę stabilności zespołom deweloperskim w ramach trzyletniego LTS (Long Term Support).
Lepsza diagnostyka i integracja multiplatformowa
Zmiany wprowadzone w platformie .NET 6 są kontynuacją obranego przez Microsoft wcześniej kierunku. Poza poprawą diagnostyki i integracji z chmurą (wprowadzono OpenTelemetry i dotnet monitor dla Azure App Service), najnowsze SDK wprowadza szereg zmian poprawiających wydajność poszczególnych elementów środowiska .NET. Mocniejszy akcent postawiono na multiplatformowość, poprzez ujednolicenie bibliotek i ułatwienie współdzielenia kodu pomiędzy aplikacjami chmurowymi, IoT, mobilnymi, czy projektami stricte webowymi. Miłym zaskoczeniem jest też wprowadzenie natywnej wersji SDK dla komputerów opartych o architekturę Arm64.
Najnowszy .NET 6 to duża porcja pozytywnych i użytecznych modyfikacji zaserwowana nam przez Microsoft. Wiele z nich to kontynuacja wytyczonego wcześniej kierunku zmian, ale jest wśród nich kilka nieoczywistych pomysłów, które mogą przyczynić się do szybszego i bardziej efektywnego tworzenia aplikacji. Ponadto, dzięki szeregowi zmian optymalizujących wydajność oraz wprowadzeniu 3 letniego okresu wsparcia widać, że w aktualnej edycji ważnym aspektem była stabilność i dbałość o jakość udostępnionej wersji SDK.
Szczegółowa lista zmian jest naprawdę imponująca. Dużym game-changerem – szczególnie dla web deweloperów i osób zaczynających przygodę z .NET – wydaje się być wprowadzenie Minimal API pozwalającego na szybkie utworzenie prostej aplikacji internetowej. W .NET 6 standardowa aplikacja “Hello world” może zostać ograniczona zaledwie do kilku linijek kodu. Zamiast dotychczasowego definiowania rozbudowanej konfiguracji – w ramach klas Program i Startup – wystarczą zainicjowanie Buildera strony i wystartowanie aplikacji:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Obsługa standardu HTTP/3 i poprawa wydajności Json API
Kolejnym kamieniem milowym środowiska w wersji 6 jest wprowadzenie w .NET Core, API do obsługi standardu HTTP/3. Dzięki temu możliwe stało się nawiązywanie połączeń klient-serwer przy użyciu tego najnowszego, choć wciąż rozwijanego i popularyzowanego, standardu. W ramach prac nad tym API zaimplementowano obsługę nowego protokołu Quic w bibliotece MSQuic, której kod udostępniono na zasadzie open source. Sama konfiguracja API dla HTTP/3 ma uproszczoną formę nawiązującą do wspomnianego wcześniej Minimal API:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, options) =>
{
options.Listen(IPAddress.Any, 5001, listenOptions =>
{ // Use HTTP/3
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listenOptions.UseHttps();
});
});
Z punktu widzenia zespołów tworzących aplikacje webowe, ważnym usprawnieniem jest poprawa wydajności Json API poprzez wprowadzenie source generators w pakiecie System.Text.Json. Zamiast dotychczasowego mechanizmu definiującego serializację metadanych obiektu w trakcie trwania runtime aplikacji otrzymaliśmy nowy, dodatkowy tryb działający w momencie budowania aplikacji (compile time). Przeniesienie tego bazującego na refleksji mechanizmu pozwoliło na uzyskanie znacznej poprawy wydajności czasowej Json API oraz redukcję zużycia pamięci w stosunku do poprzednich wersji SDK.
Więcej możliwości frameworków Blazor i języka JavaScript
Nowa wersja środowiska .NET to także znaczne poszerzenie możliwości Blazor’a. Zakres zmian wprowadzonych dla tego stosunkowo nowego (jak na .NET) frameworka wyraźnie pokazuje jak ważną technologią stał się Blazor w ramach środowiska rozwijanego przez Microsoft. Z jednej strony wyraźnie poprawiono wydajność i czas kompilacji aplikacji Blazor oraz wprowadzono AOT (Ahead Of Time compilation) pozwalające na bezpośrednią kompilację do Web Assembly.
Zmiany wprowadzone w platformie .NET 6 są kontynuacją obranego przez Microsoft wcześniej kierunku. Poza poprawą diagnostyki i integracji z chmurą (wprowadzono OpenTelemetry i dotnet monitor dla Azure App Service), najnowsze SDK wprowadza szereg zmian poprawiających wydajność poszczególnych elementów środowiska .NET. Mocniejszy akcent postawiono na multiplatformowość, poprzez ujednolicenie bibliotek i ułatwienie współdzielenia kodu pomiędzy aplikacjami chmurowymi, IoT, mobilnymi, czy projektami stricte webowymi.
Z drugiej strony nie zapomniano o aspektach takich, jak rozszerzenie wsparcia dla JavaScript, czy poprawiono obsługę komponentów generycznych. Ponadto wprowadzono komponenty dynamiczne, których wygląd jest uzależniony od przekazanego modelu i opisu zawartego w plikach konfiguracyjnych. Od aktualnej wersji .NET uzyskaliśmy też możliwość modyfikacji sekcjistrony, dzięki czemu swobodnie możemy zmieniać nagłówek budowanej strony – dostosowując go do aktualnych potrzeb – bez konieczności używania różnorakich obejść istniejącego dotąd problemu.
Nowości w języku C# 10
Dla back-end deweloperów najbardziej interesujące zmiany wprowadzono w samym C#, który to zaktualizowano do wersji 10. W nowej edycji jednego z najpopularniejszych języków programowania pojawiły się usprawnienia takie jak:
- Global i implicit usings pozwalające na ograniczenie liczby referencji w nagłówku plików klasy.
global using System;
global using static System.Console;
global using Env = System.Environment;
Dzięki możliwości zadeklarowania powtarzających się using’ów w jednym miejscu (np. klasie Program.cs) dostaliśmy możliwość dosyć wygodnego wyczyszczenia kodu z nadmiarowych linii, uwspólniania nazewnictwa aliasów w ramach projektu i ułatwienia zarządzania zestawem bibliotek w ramach projektu.Aby włączyć ten tryb należy zmodyfikować csproj:
<propertygroup>
<!-- Other properties like OutputType and TargetFramework -->
<implicitusings>enable</implicitusings></propertygroup> - Automatyczne wykrywanie typów “naturalnych” w lambda expressions, co likwiduje konieczność określania Target Type:
var parse = (string s) => int.Parse(s);
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>
Jak widać po powyższym przykładzie ta z pozoru kosmetyczna zmiana znacząco poprawia czytelność kodu i ułatwia ewentualny refactoring. Ponadto dzięki automatycznemu wykryciu typu, umożliwione zostało rzutowanie na typy ogólne takie jak object, czy Delegate. - Poprawa obsługi typów struct. W C# 10 pojawiły się bezparametrowe konstruktory struktur oraz wprowadzenie możliwości inicjacji pól i właściwości struktury poprzez field initialized. Dzięki temu znacząco ułatwiono przypisywanie wartości domyślnych dla struktur, tak jak to było dotąd możliwe tylko dla klas.
public struct Address
{
public Address()
{
City = "";
}
public string City { get; init; }
} - Wprowadzenie record struct będącego odpowiednikiem record class z C# 9 jako ujednolicenie funkcjonalności pomiędzy strukturami, a klasami. Dodatkowym wyróżnikiem funkcjonalnym record struct jest dodanie implementacji IEquatable i operatora ==.
public record struct Person
{ public string FirstName { get; init; } public string LastName { get; init; }} - Namespace pliku od teraz nie musi być oznaczony nawiasami. Zamiast tradycyjnego zestawu klamer wystarczy średnik na końcu, co pozwala na zmniejszenie ilości zagnieżdżeń w pliku klasy:
namespace MyCompany.MyNamespace;class MyClass { ... }
- Dodano możliwość deklaracji typu obiektu zwracanego przez lambda expression minimalizując tym samym możliwość pomyłki przez deweloperów i wprost deklarując kompilatorowi jakiego typu oczekujemy:
var a = object (bool b) => b ? 1 : "two"; // Func<bool, object>
- Lambda expressions otrzymały możliwość używania atrybutów. Dzięki temu w znaczny sposób ułatwione zostało rozpoznawanie lambda expressions mechanizmem refleksji.
Func<string, int> parse = [Example(1)] (s) => int.Parse(s);var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";
- Umożliwiono korzystanie z zagnieżdżonych właściwości w ramach property pattern.
Jest to rozszerzenie działania znanego z poprzednich edycji .NET, gdzie jak dotąd mogliśmy odnosić się do właściwości obiektu, bez łatwego przeszukiwania wartości zagłębionych w obiektach podrzędnych. Zmiana ta podyktowana była przede wszystkim ułatwieniem w odnoszeniu się do wartości zaszytych w obiektach będących właściwością obiektu nadrzędnego:
if (obj is Person { Address.City: "Seattle"}) // Extended property pattern
Console.WriteLine("Seattle");
Zmiany w SDK związane z FileStream
Warto wspomnieć też o poprawie wydajności operacji I/O w ramach FileStream. Ta nieco pomijana i uznawana za oczywistą część SDK, z którą spotyka się praktycznie każdy programista back-end uzyskała znaczne usprawnienia na skutek wprowadzenia możliwości pre-alokacji pamięci oraz dzięki rozszerzeniu konstruktora klasy FileStream:
async Task AllOrNothingAsync(string path, IReadOnlyList<ReadOnlyMemory> buffers)
{ using SafeFileHandle handle =
File.OpenHandle( path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
FileOptions.Asynchronous preallocationSize: buffers.Sum(buffer => buffer.Length)); // new API (preview 6) await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0); }
Dodatkowo do konstruktora klasy FileStream dodano możliwość podania obiektu klasy FileStreamOptions, znacząco przy tym poprawiając czytelność kodu:
public class FileStream : Stream
{
public FileStream(string path, FileStreamOptions options);
}
Aby zapewnić kompatybilność wspominanego FileStream API dodano możliwość wymuszenia kompatybilności wstecznej do wersji .NET 5. Realizowane jest to przez przestawienie flagi w runtimeconfig.json:
{ "configProperties": { "System.IO.UseNet5CompatFileStream": true }}
Przyspieszenie i bardziej efektywne tworzenie aplikacji
Na koniec w .NET 6 wprowadzono też szereg zmian w już rozbudowanym Math API. Dodano kilka nowych funkcji matematycznych:
- SinCos – jednoczesne obliczanie Sin i Cos.
- ReciprocalEstimate – obliczanie przybliżenia 1 / x.
- ReciprocalSqrtEstimate – obliczanie przybliżenia 1 / Sqrt(x).
Ponadto popracowano nad optymalizacją typu BigInteger oraz zaktualizowano metody parsujące standardowe typy danych.
Podsumowując, najnowszy .NET 6 to kolejna, duża porcja pozytywnych i użytecznych modyfikacji zaserwowana nam przez Microsoft. Wiele z nich to kontynuacja wytyczonego wcześniej kierunku zmian, ale jest wśród nich kilka nieoczywistych pomysłów, które mogą przyczynić się do szybszego i bardziej efektywnego tworzenia aplikacji. Ponadto, dzięki szeregowi zmian optymalizujących wydajność oraz wprowadzeniu 3 letniego okresu wsparcia widać, że w aktualnej edycji ważnym aspektem była stabilność i dbałość o jakość udostępnionej wersji SDK.
Michał Grzywacz, Senior .NET Developer z zespołu Business Solutions w MakoLab. Absolwent Politechniki Łódzkiej na wydziale IFE, w MakoLab od 2017 r. Z technologią .NET związany od 2010 r. Przez lata pracy w IT miał okazję uczestniczyć w różnorodnych projektach – od małych aplikacji po specjalistyczne systemy oparte o rozbudowane platformy.