Nowości w C# 8.0. Czy jest na co czekać?
Platforma .NET wraz z jej wiodącym językiem programowania C#, rozwijają się już od wielu lat, na bieżąco dostarczając przydatnych rozwiązań i wzorców. W tym momencie oczekujemy na ósmą rewizję języka C#, która w pierwotnych planach miała ujrzeć światło dzienne wraz z nowym środowiskiem IDE Visual Studio 2019. Trudności jednak zweryfikowały te plany i musimy jeszcze poczekać na nasze nowe zabawki. A czy rzeczywiście jest na co?
Marcin Ziąbek. Senior .NET Developer w CH2M. Programista pasjonat starający się zrozumieć i zebrać doświadczenie w różnych technologiach, aby użyć zgromadzoną wiedzę do rozwiązywania najtrudniejszych i nietrywialnych problemów. Skupiony na projektowanie architektury systemów informatycznych, ale również profilowaniu wydajności i optymalizacji. Uważa, że struktury danych i algorytmika, choć obecnie mocno niedoceniane i zapomniane, są potężnymi narzędziami rozwoju możliwości systemów programistycznych.
Spójrzmy do oficjalnej dokumentacji i wybierzmy najbardziej interesujące smaczki. Zaznaczam, iż artykuł ten ma na celu przedstawienie mojej opinii na temat nowej wersji języka C#, niekoniecznie dogłębne omówienie jego nowych możliwości – to w sposób niezwykle czytelny zrobili już jego autorzy.
Spis treści
Elementy programowania funkcyjnego
Po krótkiej analizie, z uśmiechem na twarzy, możemy stwierdzić, iż projektanci języka coraz bardziej kierują się ku rozwiązaniom funkcyjnym, które w sposób znaczący zmieniają sposób, w jaki projektujemy i tworzymy oprogramowanie. Nie ma się jednak co dziwić, programowanie funkcyjne w ostatnich latach staje się wyjątkowo modne. Być może dlatego, że jest czymś nowym i ekscytującym, a może ponieważ rozwiązuje wiele problemów z jakimi borykają się obecnie architekci.
Warto w tym momencie zaznaczyć, że platforma .NET dysponuje już innym językiem, który w swej naturze jest funkcyjny – mowa tutaj oczywiście o F#. Zaryzykuję nawet stwierdzenie, że F# jest prekursorem zastosowań funkcyjnych na platformie .NET, a w najnowszym C# 8 dojrzymy funkcjonalności, które od jakiegoś czasu są dostępne do wielokrotnie mniej licznej grupy pasjonatów F#. Przypomnę tylko, że oba języki stają się więc multiparadygmatowe, jako że oba dysponują cechami programowania obiektowego i funkcyjnego – podchodzą jednak do obu zagadnień z różnych perspektyw.
Pattern matching
Wersja 8 języka C# kontynuuje prace związane z wdrożeniem wzorca Pattern Matching. Otrzymaliśmy do tej pory podstawowe typy wzorcowe realizowane za pomocą słowa kluczowego „is” wraz z udoskonalonymi wyrażeniami „switch”. Tym razem zdecydowano się na dalszy lifting deklaracji switch – osoby, które programowały w języku Kotlin zauważą spore podobieństwo. Możemy więc zaobserwować uproszczenie składni za pomocą operatora używanego już w wyrażeniach lambda, czy wprowadzenie alternatywnego sposobu obsługi wartości domyślnej (poprzez znak podkreślenia). Nie ulega wątpliwości, że zmiany te są głównie cukrem syntaktycznym tworzącym podstawę pod dalsze prace nad językiem.
W istocie, zmiany są dużo bardziej skomplikowane. Możliwości dopasowywania pozwalają na przyrównywanie określonych właściwości obiektów do konkretnych wartości i wykonywanie na ich podstawie zdefiniowanych operacji. W podobny sposób można używać krotek, a nawet dokonywać dekonstrukcji obiektu w celu łatwiejszej jego obsługi. Cóż, ciężko opisać jak wielkie możliwości dają nowe zasady syntaktyczne języka – jedno jest jednak pewne, tego typu możliwości wymagają bardzo dużej świadomości programisty i odpowiedzialności. W rzeczy samej, pattern matching to nic innego jak dodatkowa warstwa oparta o zwykłe instrukcje warunkowe, co w efekcie pozwala ograniczyć ilość kodu i uczynić go bardziej czytelnym. Osobiście jednak czuję, że w poprawnym zastosowaniu tego typu funkcjonalności pomogą nam dopiero takie narzędzia jak ReSharper.
Modyfikator using dla obiektów IDisposable
Programiści, którzy na co dzień pracują z zasobami, np. dostępem do plików, połączeniami z bazą danych, wiedzą pewnie doskonale, czym charakteryzuje się interfejs IDisposable i jakie wymagania stawia przed programistą. Konieczność zwolnienia zasobu bez względu na powodzenie algorytmu jest jednak bardzo trudne do zapewniania i w praktyce zmusza do zastosowania mało eleganckiej konstrukcji try…finally. Częściowym rozwiązaniem tego problemu było wprowadzenie dodatkowego przypadku użycia dla słowa kluczowego using, które zapewnia dostęp do zasobu w ramach swojego ciała i jego zwolnienie po opuszczeniu owego. Wciąż jednak taki zapis zmusza do zastosowania wcięcia w kodzie. Od teraz, wszystkie zasoby będzie można zadeklarować za pomocą modyfikatora o takiej samej nazwie. Moim zdaniem jest to wspaniała zmiana!
Statyczne funkcje lokalne
Niedawno otrzymaliśmy bardzo ciekawy koncept funkcji lokalnych, czyli funkcji deklarowanych wewnątrz metod klasy. Trzeba jednak pamiętać, że choć jest to niezwykle przydatne, niesie jednak ze sobą kilka pułapek. Jedną z nich jest możliwość przechwyceniu zmiennej funkcji-rodzica przez funkcję lokalną. Taka sytuacja, choć niejednokrotnie bardzo przydatna, może powodować trudności w zrozumieniu algorytmu. Od teraz możemy zablokować taką możliwość korzystając ze statycznych funkcji lokalnych, które mogą posługiwać się tylko swoimi argumentami. Takie funkcje są więc sprowadzone do roli funkcji pomocniczych pojedynczego wykorzystania, jest to więc metoda organizacji kodu wewnątrz klasy.
Koniec z null-ami w zmiennych referencyjnych
Kolejnym ciekawym tematem jest zmiana traktowania obiektów referencyjnych. Język C#, podobnie jak większość nowoczesnych języków programowania, zezwala na przechowywanie wartości null pod daną zmienną. Nie trudno się domyślić, do jakich sytuacji może doprowadzić taka ewentualność, wszakże NullReferenceException jest jednym z najczęstszych wyjątków w naszych aplikacjach. C# pozwoli na traktowanie obiektów referencyjnych jako nie posiadających możliwości przypisania wartości null, co przybliży je pod względem zachowania do struktur i większości typów wbudowanych. Możliwość zapisania wartości null będzie wymagała użycia konstrukcji Optional<T>, bądź też jej cukru syntaktycznego w postaci operatora pytajnika za nazwą typu (T?).
Pojawia się więc pytanie, czy zmiana ta jest rewolucyjna? Niestety, obawiam się, że absolutnie nie jest. Ze względów kompatybilności wstecznej, nie istnieje możliwość wprowadzenia nowych zasad. Początkowo rozważano ich egzekucję za pomocą specjalnej, dodatkowej flagi używanej na etapie kompilacji, obecnie jednak projektanci języka zdecydowali się na użycie ostrzeżeń. W takim przypadku należy zauważyć, że działanie edytora programistycznego będzie łudząco podobne do funkcjonalności narzędzia ReSharper, które również potrafi zaoferować tego typu analizę statyczną kodu. Mało tego, oferuje dwa smaki tego typu działania: analizę optymistyczną (domyślnie aktywna), która zakłada, iż argumenty metody nie mają wartości null oraz pesymistyczną, która jest dużo bardziej restrykcyjna i stawia pod znakiem zapytania bezpieczeństwo wszystkich argumentów i wartości pól / właściwości.
Ulepszone indeksatory kolekcji
Ostatnią ciekawą funkcjonalnością, jaką chciałbym omówić jest nowy sposób indeksacji kolekcji. Programiści, którzy mieli chociaż trochę do czynienia z językiem Python, prawdopodobnie pamiętają przynajmniej dwie jego cechy: organizację kodu realizowaną przy pomocy wcięć, a nie nawiasów klamrowych oraz genialny indeksator kolekcji. Z radością stwierdzam, iż podobny mechanizm trafia również do C# — pozwalając w prosty sposób wybierać elementy pomiędzy dowolnymi indeksami, w tym również stosować indeksowanie odwrócone. Podobnie jak interpolacja tekstów realizowanych przez operator $, tutaj również zastosowano specjalną klasę pośrednią, to jest jednak kwestia na osobną opowieść dla tych nieco bardziej zaawansowanych.
Ewolucja czy rewolucja?
Na koniec pojawia się pytanie, czy nowe możliwości języka rzeczywiście pozwolą na zwiększenie czytelności kodu? Uważam, że zdania mogą być podzielone. Nowe przypadki użycia operatora using, bardziej restrykcyjne traktowanie obiektów referencyjnych, statyczne funkcje lokalne, bądź nowy sposób indeksacji – te zmiany trafiają do grupy moich ulubionych, które zdefiniują kolejne lata mojego programowania, podobnie jak zrobiły to interpolacja łańcuchów tekstowych, extension metody i operator koelescencji (operator warunkowy ??).
Pattern matching również wydaje się bardzo interesujący, jednak podobnie jak krotki może spowodować skonfundowanie u programistów nie interesujących się najnowszymi trendami (co nie jest usprawiedliwieniem!), ale przede wszystkim powstawanie skomplikowanych i nieczytelnych tworów syntaktycznych. Cóż, warto się chwilę zastanowić i wrócić do kodu. Powodzenia!
Zdjęcie główne artykułu pochodzi z stocksnap.io.