Blog

FIS-SST

Co przyniesie przyszłość C#?

Tegoroczny maj trzymał nas przy ekranach. Przenieśliśmy życie – zarówno zawodowe, jak i społeczne – na platformy online, starając się odnaleźć w nowej rzeczywistości.

O wpływie pracy zdalnej na produktywność można się rozwodzić, ale niezaprzeczalnie zespół Microsoftu dobrze sobie poradził – pojawił się preview najnowszej wersji: nabierającego kształtu C# 9.0.

Programiści Microsoftu wskazują, że z każdą nową wersją języka C#, dążą do większej przejrzystości i prostoty w typowych scenariuszach kodowania[i]. Tym razem za szczególny cel obrano wspieranie zwięzłej i niemutowalnej (immutable) reprezentacji danych.

Co więc przyniesie przyszłość C#? Przekonajmy się!

Jak zacząć?

Aby samemu przekonać się o możliwościach C# 9.0, wymagana jest instalacja .NET Runtime 5.0[ii] oraz Visual Studio Preview[iii]. Po stworzeniu projektu należy upewnić się, że plik *.csproj zawiera następujące właściwości:

Właściwości tylko inicjujące (init-only)

Jak wiemy, inicjatorów można użyć do zainicjowania obiektów, bez jawnego wywołania konstruktora dla danego typu.

Dzięki elastyczności i czytelnemu formatowi, szczególnie dobre zastosowanie znajdują przy tworzeniu obiektów zagnieżdżonych, kiedy całe drzewo obiektów inicjujemy za jednym razem.

Rozważmy prostą klasę:

Oraz jej inicjator:

Obecnie podstawowym ograniczeniem jest wymuszona modyfikowalność właściwości. Aby inicjator zadziałał, wywoływany jest najpierw domyślny, bezparametrowy konstruktor obiektu, a następnie wartości przypisywane są odpowiadającym metodom setter.

C# 9 niesie ze sobą akcesor init – wariant metody dostępu set, który można wywołać tylko podczas inicjalizacji obiektu.

Z powyższą deklaracją, przedstawiony wcześniej inicjator wciąż jest poprawny, ale wszystkie późniejsze próby przypisania wartości Perimeter i Area będą skutkowały błędem.

Rekordy

Pozostając w temacie niemutowalnych danych, init-only znajdują swoje zastosowanie, jeśli chcemy aby poszczególne właściwości były niezmienne. W przypadku, kiedy chcielibyśmy, aby cały obiekt był niemutowalny i zachowywał się jak wartość, można rozważyć jego deklarację jako rekordu. Używamy wtedy słowa kluczowego data.

Ogólnie rzecz biorąc, rekordy mają być bardziej postrzegane jako wartości, tj. dane, a mniej jako obiekty. Z ich pomocą reprezentujemy zmiany w czasie – tworzymy nowe rekordy, odzwierciedlające nowy stan. Są definiowane przez swoją zawartość, a nie tożsamość i w taki też sposób określana jest ich równość.

Wyrażenie with

Aby reprezentować nowy stan niemutowalnych danych, typowym wzorcem jest tworzenie nowych wartości z istniejących.

Przykładowo, gdyby nasza figura zmieniła swój obwód, stając się z kwadratu o boku 10, prostokątem o bokach 1 i 100, przedstawilibyśmy to jako nowy obiekt, który jest kopią starego (pole bez zmian), ale z innym obwodem.

Zamiast reprezentować figurę w czasie, zapis ma przedstawiać stan figury w danym czasie. Rekordy pozwalają na to z nowym rodzajem wyrażenia – wyrażeniem with:

Aby określić, co różni się w nowym obiekcie od starego, wyrażenia with używają składni inicjatora obiektu. Możliwe jest określanie wielu właściwości.

Wszystkie powyższe operacje dzieją się przy pomocy konstruktora kopiującego, niejawnie definiowanego przez rekord i wywoływanego przez wyrażenie with.

Równość oparta na wartościach

Rekordy, podobnie jak struktury, odznaczają się równością opartą na wartościach. Każde pole porównywane jest przez rekurencyjne wywołanie Equals. Nadpisuje to wirtualną metodę Object.Equals(object1, object2), skutkując tym, że dwa obiekty rekordów mogą być sobie równe, nie będąc tym samym obiektem.

Przykładowo, ponownie zmodyfikujmy obwód zmodyfikowanej figury:

Wróciliśmy tym samym do początkowych wartości pierwszego zdefiniowanego obiektu.

Mielibyśmy teraz ReferenceEquals(originalFigure, figure) = false (ponieważ nie są tym samym obiektem), ale Equals(originalFigure, figure) = true (mają te same wartości: obwód równy 40
i pole równe 100).

Skrócona deklaracja

Rekordy są docelowo niezmienne, z publicznymi właściwościami tylko dla init.

Zamiast niejawnie prywatnego pola, jak w innych deklaracjach klas i struktur, w rekordach przyjmuje się, że jest to skrót dla publicznej właściwości automatycznej, tylko do inicjalizacji.

Stąd deklaracja:

Oznacza dokładnie to samo, co wcześniejsza:

Wyrażenie with i dziedziczenie

Aby najlepiej zobrazować współdzielenie funkcjonalności, dodajmy pochodną klasy Figure.

Stwórzmy Rectangle, ale przechowując go w obiekcie typu Figure.

Aby nowa figura była właściwą kopią, musi być obiektem Rectangle, z tym samym Id, co kopiowana pierwsza.

Nowy C# umożliwia przeprowadzenie tak złożonej operacji. Rekordy mają ukrytą wirtualną metodę, której powierza się „klonowanie” całego obiektu. Każdy typ rekordu pochodnego przesłania tę metodę, a konstruktor kopiujący rekordu pochodnego łączy się z konstruktorem kopiującym rekordu podstawowego. Wyrażenie with wywołuje ukrytą metodę „clone” i stosuje inicjator obiektu do wyniku.

Programy na najwyższym poziomie

Dotychczas przygody każdego początkującego programisty z C#, zaczynały się od standardowego widoku najkrótszego programu w Visual Studio:

C# 9.0 oferuje napisanie tego samego w zupełnie nowy sposób, upodobniając się tym samym do zwięzłych składni języków funkcyjnych, takich jak Python i F#:

Funkcja Main() zadeklarowana jest na najwyższym poziomie, bez zagnieżdżania się. Dostęp do argumentów wiersza poleceń można dalej uzyskać poprzez args, określane tutaj jako „magiczny” parametr.

Ulepszone dopasowanie wzorców

Wraz z C# 8.0 dostarczono składnię dopasowania wzorca, zapewniającą skuteczny mechanizm używania i przetwarzania danych.

Obecnie, aby sprawdzić typ, musimy zadeklarować identyfikator:

C# 9.0 umożliwia jego pominięcie:

Wyrażenia new z typem docelowym

Wyrażenie new prawie zawsze wymagało określenia typu. Teraz możliwe będzie jego pominięcie, jeśli istnieje wyraźny typ, do którego przypisywane są wyrażenia.

Typy docelowe ?? i ?:

Czasami wyrażenia warunkowe ?? i ?: nie mają oczywistego typu współdzielonego.

Obecnie kompilator nie pozwoliłby na poniższy przykład, ale C# 9.0 nie będzie miał nic przeciwko, jeśli istnieje typ docelowy, na który obie strony konwertują:

Native Ints

C# 9.0 ma wprowadzić nowy zestaw typów natywnych – nint i nuint. Celem jest umożliwienie jednemu plikowi źródłowemu C# korzystanie z 32-bitowej lub 64-bitowej pamięci w zależności od typu platformy hosta i ustawień kompilacji.

Motywacją do wprowadzenia nowych typów było ich wykorzystanie w scenariuszach międzyoperacyjnych i bibliotekach niskiego poziomu.

Słowo kluczowe static dla wyrażeń lambda

Kolejną nowością jest użycie słowa kluczowego „static” w wyrażeniach lambda. Ma to na celu powstrzymanie anonimowych metod przez przechwytywaniem lokalnych wartości i parametrów.

Przyjrzyjmy się przykładowi:

Kod powyżej pokazuje przechwycenia „y” przez wyrażenie lambda.

Z nowym, proponowanym rozwiązaniem, słowo kluczowe „static” może być użyte to określenia tego jako błąd.

Podsumowanie

Powyżej przedstawione zostały najważniejsze funkcjonalności wciąż rozwijającego się C# 9.0. Ich pełną listę jak i postęp prac można śledzić na GitHubie[i].

Nowe funkcje są stale adaptowane z innych języków (F#, Kotlin, czy Typescript), aby dostarczyć użytkownikom pożądane funkcjonalności, rozszerzyć istniejące, albo po prostu „dosypać” trochę cukru składniowego.

Pytając o przyszłość C#, jedno jest pewne: na pewno jest to premiera na którą warto czekać.

[1] https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/

[1] https://dotnet.microsoft.com/download/dotnet/5.0

[1] https://visualstudio.microsoft.com/pl/vs/preview/ [1] https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md

Formularz kontaktowy

Patrycja Pałus

Office Administrative Assistant

Zapraszam do kontaktu telefonicznego lub mailowego

Newsletter FIS-SST

To continue downloading please subscribe to our newsletter 

Newsletter FIS-SST

Aby kontynuować ściąganie prosimy zapisz się do naszego newsletter`a