C# 9.0 a zwięzłość kodu

Częstotliwość wydawania kolejnych wersji języka C# zdecydowanie wzrosła w ostatniej dekadzie. Kurz po opublikowaniu wersji 8.0 jeszcze nie opadł, a prace nad kolejną wersją już się zaczęły. Oficjalne repozytorium github języka C# zawiera listę funkcjonalności, które potencjalnie mogą zostać dodane do nowej wersji języka. Bazując na informacjach z wyżej wymienionej strony, nowa wersja ma się pojawić pierwszego stycznia 2090. Data mocno wybiega w przyszłość, aby nie deklarować się jednoznacznie, jednak analizując obecne trendy można się spodziewać nowej wersji w okolicach połowy 2021. Progres można śledzić tutaj: Language Feature Status. W poniższym artykule skupimy się wyłącznie na funkcjonalnościach, które pozwolą pisać bardziej zwięzły kod.

1. Simplified parameter null validation code – Uproszczone walidowanie parametrów null-owalnych

W celu minimalizacji pisanego kodu zaproponowano lukier składniowy. Zamiast ręcznego sprawdzania, czy parametr jest null-em i rzucania wyjątkiem ArgumentNullException będzie można po prostu użyć znaku ‚!’ po nazwie zmiennej. Szczegóły.


//Old version
void Process(string valueOne, string valueTwo)
{
	if (valueOne is null)
		throw new ArgumentNullException(nameof(valueOne));
	if (valueTwo is null)
		throw new ArgumentNullException(nameof(valueTwo));
...
}


// New version, char ! was added after parameter names
void Process(string valueOne!, string valueTwo!)
{
...
}

2. Primary constructors

Kolejny lukier składniowy, który pozwoli nam zaoszczędzić kilka kliknięć w klawiaturę oraz usunąć Boilerplate code. Obecnie jeżeli chcemy przypisać wartości do właściwości klasy to musimy pisać bardzo powtarzalny kod w konstruktorze. Oczywiście możemy ten kod wygenerować w VisualStudio na podstawie istniejących właściwości poprzez naciśnięcie ctrl+. i wybranie opcji GenerateConstructor. Nowe podejście będzie wymagać tylko zdefiniowania zmiennych w konstruktorze. Cała reszta zostanie wygenerowana automatycznie pod spodem w Intermediate Language. Opisywana funkcjonalność jest już stosowana w innych językach jak Kotlin czy TypeScript. Szczegóły.

//Old version
    public class UsersListViewModel
    {
        private readonly UserRepository _repository;
        private readonly ViewModelConfiguration _config;

        public UsersListViewModel(UserRepository repository, ViewModelConfiguration config)
        {
            _repository = repository;
            _config = config;
        }
    }

//New version
    public class UsersListViewModel
    {
        public UsersListViewModel(UserRepository _repository, ViewModelConfiguration _config)
        {
        }
    }

3. Target-typed new expressions – Dobieranie typu dla New na podstawie typu docelowego

Bardzo ciekawa koncepcja aby słowo kluczowe ‚new’ było bardziej inteligentne. Nowo tworzony typ będzie wnioskowany z kontekstu. Nowe podejście pozwoli tworzyć bardziej zwięzły i czytelny kod. Załóżmy, że musimy utworzyć słownik, który dla każdego klucza będzie przechowywał listę ciągów znaków.. Słowo ‚List’ będzie musiało się przewijać w naszym kodzie parokrotnie. W nowym podejściu będziemy mogli pominąć typ przy słowie new. Szczegóły.

//Old version
Dictionary<string, List<string>> dividers = new Dictionary<string, List<string>>() 
{
	{ "ten", new List<string>(){ "one", "two", "five", "ten" } },
	{ "fifteen", new List<string>(){ "one", "three", "five", "fifteen" } },
	...
	{ "twenty", new List<string>() { "one", "two", "four", "five", "ten", "ten", "twenty" } }
};

//New version, List<string> statement doesn't have to be repeated in each line
Dictionary<string, List<string>> dividers = new() 
{
	{ "ten", new(){ "one", "two", "five", "ten" } },
	{ "fifteen", new(){ "one", "three", "five", "fifteen" } },
	...
	{ "twenty", new() { "one", "two", "four", "five", "ten", "ten", "twenty" } }
};

4. Init-only properties – Właściwości tylko do inicjalizacji

Kolejny ukłon w kierunku ułatwienia programowania funkcyjnego z c#. Obecnie, jeżeli chcemy utworzyć klasę, której obiekty będą niemutowalne to wszystkie właściwości muszą mieć zdefiniowany zakres widoczności dla akcesorów ‚set’ jako prywatny. Wartości mogą być przekazywane tylko poprzez konstruktory. Minusem tego podejścia jest brak możliwości użycia inicjalizatorów. Dla przypomnienia inicjalizatory są wykorzystywane do tworzenia złożonych obiektów w ramach jednej instrukcji.

//Old version - Mutable class
public class Address
{
	public string Country { get; set;}
}

public class Employee
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public Address Address { get; set; }
}

//Object initializer
var newEmployee = new Employee { 
	FirstName = "Mike", 
	LastName = "Spenser", 
	Address = new Address { Country = "Poland" }
}

//Old version - Immutable class, Object initializer cannot be used
public class Address
{
	public string Country { get; private set;}
	
	Address(string country) {
		...
	}
}

public class Employee
{
	public string FirstName { get; private set; }
	public string LastName { get; private set; }
	public Address Address { get; private set; }
	
	Employee(string firstName, string lastName, Address address) {
		...
	}
}

Rozwiązaniem tego problemu jest nowe słowo kluczowe ‚init’ – alternatywa dla ‚set’. Właściwość będzie można ustawić tylko raz podczas inicjalizacji. Kolejne próby przypisania wartości będą skutkować zwróceniem błędu na etapie kompilacji. Nowe słowo kluczowe będzie można oczywiście używać z opcją ‚readonly’. Wynikiem zmian będzie bardziej skompresowany kod.

//New version,  Object initializer can be used
public class Address
{
	public string Country { get; init;}
}

public class Employee
{
	public string FirstName { get; init; }
	public string LastName { get; init; }
	public Address Address { get; init; }
}

5. Top-level programs

Pisanie prostych programów, czy testowanie działania pojedynczych funkcji wymaga od programistów dodania dużo pobocznego kodu. Narzut zbędnego kodu, klamr, wcięć może być odstraszający dla osób, które dopiero zaczynają swoją przygodę z programowaniem. Celem nowej funkcjonalności jest uproszczenie i zwiększenie przejrzystości. W aplikacjach, które zawierają tylko jeden plik będzie można pominać przestrzenie nazw oraz definicje punktu wejściowego do aplikacji. Mechanizm będzie wspierał zwracanie kodu wyjściowego, przyjmowanie parametrów wejściowych oraz operacje na await. Wymagane będzie tylko dodanie using-ów. Szczegóły

//Old version
using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

//New version
using System;

Console.WriteLine("Hello World!");

Podsumowanie

Opisane funkcjonalności ewidentnie mają na celu usunięcie zbędnego kodu, który nie zawiera w sobie żadnych istotnych informacji. Nowe zmiany pozwolą pisać kod szybciej oraz czyściej. Osoby zaczynające, zamiast skupiać się na składni będą mogły się bardziej skupić na algorytmach lub logice biznesowej. Niestety wszystko odbywa się kosztem ukrytej złożoności.

W następnym wpisie przyjrzymy się dokadniej, jak nowe zmiany zbliżają język c# do programowania funkcyjnego poprzez wprowadzenie Rekordów.

Źródła:

Przyłącz się do konwersacji

1 komentarz

  1. Chicałbym zauważyć że większość z tych nowości już od dłuższego czasu dostepna jest w F#’pie, i są to kolejne eelementy tego języka sprowadzane do C#.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *