Nowości w C# 8

Tak jak pisałem wcześniej, wole napisać o nowościach jak już je dobrze poznam więc stąd takie opóźnienie. Po drugie cześć osób i tak nie używa wszystkich nowości, bo tak szybko wychodzą więc warto co jakiś czas o tym napisać aby przypomnieć sobie. Zapraszam na nowości w C# 8 , które weszły w 2019 roku. Napisałem je w skróconej wersji z małymi komentarzami aby było łatwiej je zapamiętać.

Spis treści

Indeksy i zakresy

Dodano ułatwienia w pracy nad elementami lub całymi zakresami wartości tablicowych.

Wprowadzony indeksy oznaczone jako ^1 jako ostatni element, ^2 jako drugi od końca element itd.

char[] sample = new char[] { 'A', 'B', 'C', 'D','E' };

var lastElement = sample[^1];        // E ostatni element
var secondLastElement = sample[^2];  // D przed ostatni element 
var firstTwo = sample[..3];          // A B C z całej tablicy weź 3 elementy. Idź tak długo jak będziesz mieć 3
var lastThr = sample[2..];           // C D E pomiń 2 pierwsze elemeny. Pomijaj 2 elemeny a potem daj resztę
var middleOne = sample[2..3];        // C pomiń 2 pierwsze elemeny ale idź tak długo jak będziesz mieć 3 elemeny (razem z tymi pominiętymi) 
var lastTwo = sample[^2..];          // D E daj 2 ostatnie (^) elemeny

Możemy też dzielić tablice na części tak jak sobie życzym.

Powstały też typy, w których można przechowywać te wyrażenia

Index lastElementType = ^1;
Range middleOneType = 2..3;

Null-coalescing assigment

Od teraz można przypisywać do zmiennej jeśli jest null-em w tenm sposób

 List<int> test = null;

 //zamiast

 if (test == null)
 {
     test = new List<int>();
 }

 //można użyć tego

 test ??= new List<int>();

Deklaracje using

Od teraz możemy w deklaracjach using zrezygnować z nawiasów i postawić na końcu średnik. Funkcja using nadal będzie działać a metoda dispose wywoła się po wyjściu z danego bloku kodu. Czyli w tym przypadku po wyjściu z deklaracji if-a

public void UsingTests()
{
	if (true)
    {
		using var fr = new StreamReader(@"C:\");
    }
}

Modyfikator readonly w strukturach

Od teraz możemy w metodach w strukturze zadeklarować, że dana metoda nie może zmieniać pól.

Domyślnie metody w interfejsach

Jeden przykład 1000 słów. Od teraz można zdefiniować domyślnie implementacje metod w interfejsach, dzięki temu nie musimy nic zmieniać w implementacjach już istniejących w klasach.

public interface INew
{
  void New();
  int Add(int x, int y) => x + y;
}

Aby jednak wykonać taką metoda trzeba ją wywołać explicitly

 var test = ((INew)new NewClass()).Add(2, 3);

Można też definiować statyczne pola, które będą dostępne w danej implementacji metody w interfejsie albo…

 public interface INew
 {
  void New();
  int Add(int x, int y) => x + y + Handicap;
  static int Handicap = 3;
 }

…na zewnątrz poprzez interfejs.

INew.Handicap = 3;

Wyrażenie switch

Od teraz można korzystać z instrukcji switch w wyrażeniach (czyli tak jak by w jednej długiej instrukcji)

 var cardNumber = 2;

 string name = cardNumber switch
 {
     1 => "First",
     2 => "Second",
     3 => "Last",
     _ => "Unknown"
 };

Z doświadczenia wiem, że warto zapamiętać, że po danym case-sie, należy wstawić przecinek, jest to czasem frustrujące gdy nie chce się skompilować, bo komunikat nie zawsze jest taki oczywisty.

Tuple patterns, positional patterns i property patterns

Od teraz C# 8 wspiera trzy nowe wyrażenia, wszystkie wiążą się głownie z instrukcją switch choć nie zawsze.

Tuple patterns i positional patterns (pozwalające na dekonstruowanie wyrażeń na tuple)

 var cardNumber = 2;
 var age = 12;

 string name = (cardNumber, age) switch
 {
     (1,3) => "First",
     (1,4) => "Second",
     (2,3) => "Last",
     _ => "Unknown"
 };

Positional patterns

Pozwalają sprawdzić obiekt razem z jego polami

 object name = "Blog";

 if (name is string { Length: 3 })
 {
     name += "Programisty";
 }

Nulowalne typy referencyjne

Od teraz można a właściwie to trzeba przy projekcie zdecydować jak się podchodzi o typów, które mogą być NULL-em. Można teraz zmusić kompilator do tego aby dawał ostrzeżenia jeśli dany typ nie jest zaznaczony, że może być nullowalny wprost.

Wygląda dość niewinnie ale w projekcie zaczyna być z tym pewnie zamieszanie. VS dużo podkreśla i co chwilę trzeba sprawdzić czy to błąd czy tylko ostrzeżenie. Czasem ciężko się domyśleć czy i dlaczego podkreśla i wysyła ostrzeżenie, bo w kodzie wygląda tak, że dana wartość nie może przyjąć null-a.

Ostrzeżenie zniknie, jeśli damy znać kompilatorowi, że wiemy co robimy

Przydatny tez okazuje się operator !, który umożliwia przerwanie ciągu wywoływań kolejnych pól i metod gdy dana wartość jest null-em

Wtedy wystarczy dodać ! i po sprawie

Asynchroniczne strumienie

Od teraz można używać razem wyrażeń await w strumieniach, które zwracają elementy poprzez słowo kluczowe yield. Wcześniej nie było takiej możliwości

 public async IAsyncEnumerable<int> Range(int deley)
 {
   for (int i = 0; i < 10; i++)
   {
		await Task.Delay(deley);
		yield return i;
   }
 }

Podsumowanie

Podczas pisania o tych nowościach przypominało mi się, gdzie je mogłem zastosować a tego nie zrobiłem. Patrząc na to z takiej odległej perspektywy to wyraźnie widać, że fakt jest to spory rozwój, a nie aż tak wielki jak by się mogło wydawać. Pytanie dlaczego? Może te zmiany idą w jakiś kierunku a nie koniecznie w takim w którym, najczęściej się idzie piszą programy w .NET-cie. Wiele zmian jest bardzo takich skryptowych, ułatwiających pisanie kodu zwięzłego i nie czytelnego (wyrażenie switch, potrafią być dość duże, indeksy i zakresy). Tak czy inaczej warto je znać, bo zawsze się to przydaje na…hackathon-ach : )