Już po konferencji Connect 2018 a została jeszcze ostania mała wersja C# do omówienia. Na ten moment najnowsza. Zmiany w tej wersji są chyba najmniej popularne ze względu na skupienie się na poprawieniu efektywności w pisaniu kodu nie zarządzanego czyli skupieniu się na wydajności. Poniżej zapraszam na analizę zmian w C# 7.3.
Spis treści
- Możliwość dostępu do zmiennych z wyrażeniem fixed bez przypinania
- Możliwość przypisania nowej wartości do zmiennych oznaczonych przez ref
- Instrukcja stackalloc umożliwa inicjalizację
- Wyrażenie fixed wspiera więcej typów
- Wsparcie Typli dla opratorów == i !=
- Atrybut field dla get-ów i set-ów
- Naprawa błędu z przeciążeniem paramentu in
- Zmienne typu out w konstruktorach
- Nowe opcje kompilatora
Możliwość dostępu do zmiennych z wyrażeniem fixed bez przypinania
Wyrażenie fixed umożliwia zabezpieczenie w kodzie nie zarządzanym aby dany fragment pamięci nie był przenoszony przez GC. Aby użyć tej składni należało wykonać następujący kod:
unsafe struct S { public fixed int myFixedField[10]; } public class C73Features { private S s = new S(); private unsafe void FixedPoint() { // before C# 7.3 fixed (int* ptr = s.myFixedField) { int p_old= ptr[5]; } } }
Od wersji C# 7.3 możemy skrócić ten zapis do czegoś takiego:
private unsafe void FixedPoint() { int p_new = s.myFixedField[5]; }
Efekt jest taki sam a pisania jest znacznie mniej. Nie skomentuje czy to jest dobra zmiana, bo bardzo mało używam kodu nie zarządzanego.
Możliwość przypisania nowej wartości do lokalnych zmiennych ref
Kontynuacja zmian związanych z usprawnieniem używania wyrażenia ref. Od wersji C# 7.3 można już przypisywać do zmiennej lokalnej typu ref inna zmienną typu ref. Jak poniżej:
ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.
Instrukcja stackalloc umożliwia inicjalizację
W C# można skorzystać z inicjalizacji tablicy podczas jej deklaracji jak w przykładzie poniżej:
private void StackallocArrya() { var arr = new int[3] { 1, 2, 3 }; var arr2 = new int[] { 1, 2, 3 }; }
Od wersji C# 7.3 można taki sam zapis zastosować używając instrukcji stackalloc, przykład poniżej:
private unsafe void StackallocArrya() { int* pArr = stackalloc int[3] {1, 2, 3}; int* pArr2 = stackalloc int[] {1, 2, 3}; Span<int> arr = stackalloc [] {1, 2, 3}; }
Wyrażenie fixed wspiera więcej typów
W wersji C# 7.3 każdy obiekt, który posiada metodę GetPinnableReference(), która zwraca typ ref lub ref readolny może być użyty razem z instrukcją fixed. Poniżej przykład jak to wygląda w praktyce:
public unsafe class FixedType { public Point[] Points { get; set; } public ref Point GetPinnableReference() { return ref Points[1]; } } private unsafe void FixedAnyType(FixedType ft) { fixed (Point* d = ft ) { } }
Informacji o tym gdzie i kto to używa jest dość mało. Choć jeśli chodzi o większą elastyczność w pisaniu wydajnego kodu, taka zmiana możne być pomocna. Sam nie używam wiele kodu nie zarządzanego, jednak wyobrażam sobie, że takie podejście może być bardzo pomocne.
Wsparcie typu TUPLE dla opratorów == i !=
Najbardziej praktyczna zmiana ze wszystkich w C# 7.3. Od teraz typy Tuple można porównywać używając operatorów == i !=. Poniżej lista wszystkich możliwych porównań. Generalnie działa to dość intuicyjnie. Nazwy pól nie mają znaczenia, typy można porównać jeśli można zrobić rzutowanie. Polecam prześledzić dokładnie tą listę raz i porządnie a na pewno zostanie ona w pamięci:
private void AssertTuple() { var left = (a: 5, b: 10); var right = (a: 5, b: 10); Console.WriteLine(left == right); // 'true' var left1 = (a: 5, b: 10); var right1 = (a: 5, b: 10); (int a, int b)? nullableTuple = right1; Console.WriteLine(left1 == nullableTuple); // 'true' // lifted conversions var left2 = (a: 5, b: 10); (int? a, int? b) nullableMembers = (5, 10); Console.WriteLine(left2 == nullableMembers); // 'true' // converted type of left is (long, long) (long a, long b) longTuple = (5, 10); Console.WriteLine(left2 == longTuple); // 'true' // comparisons performed on (long, long) tuples (long a, int b) longFirst = (5, 10); (int a, long b) longSecond = (5, 10); Console.WriteLine(longFirst == longSecond); // 'true' (int a, string b) pair = (1, "Hello"); (int z, string y) another = (1, "Hello"); Console.WriteLine(pair == another); // 'true' Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names (int, (int, int)) nestedTuple = (1, (2, 3)); Console.WriteLine(nestedTuple == (1, (2, 3)) ); }
Atrybut field dla get-ów i set-ów
W C# mamy atrybut field, który stosuje się do pół i ma on tylko tam zastosowanie. Od teraz można stosować ten atrybut też do właściwości czyli do get-ów i set-tów. Działa to tak, że dla właściwości i tak powstaje pole w IL-u więc dla tego pola ten atrybut będzie miał zastosowanie. Poniżej, krótkie wyjaśnienie:
[field: SomeThingAboutFieldAttribute] public int SomeProperty { get; set; }
W przykładzie widać, że atrybut SomeThingAboutFieldAttribute będzie zastosowany do pola, które będzie wygenerowane dla właściwości SomeProperty .
Naprawa błędu z przeciążeniem paramentu in
Tutaj sprawa jest dość prosta. Jest to nie tyle zmiana ile po prostu naprawa pewnego błędu. Błąd polegał na tym, że zastosowanie w metodzie operatora in powodowana dwuznaczność w przeciążeniach. To znaczny, kompilator nie wiedział, którą metodę ma wybrać w takim przypadku jak niżej:
static void M(S arg); static void M(in S arg);
W takim wypadku lepszym kandydatem od wersji C# 7.3 do przeciążenia będzie metoda 1 (bez in), jeśli będziemy chcieli wybrać drugą metodę musimy użyć słowa in.
Zmienne typu out w konstruktorach
C# 7.3 to dalsze usprawnienia co do typów ref i out. Tym razem mamy już możliwość używania słowa kluczowego out w konstruktorach:
public class B { public B(int i, out int j) { j = i; } } public class D : B { public D(int i) : base(i, out var j) { Console.WriteLine($"The value of 'j' is {j}"); } }
Nic dodać, nic ująć ; )
Nowe opcje kompilatora
Nowe opcje postały aby wspierać różne scenariusze DevOps.
Pierwszym nowym parametrem jest -publicsign. Umożliwia no podpisanie biblioteki używając publicznego klucza. Przykładem może być popisanie biblioteki w projektach open-source. Aby użyć tego przełącznika należy tez podać przełączniki -keyfile lub -keycontainer aby podać właściwy klucz.
Drugim nowym parametrem jest -pathmap, który mapuje ścieżki do plików z tych ze środowiska, gdzie biblioteka została skompilowana na takie jakie byśmy chcieli. Ścieżki te są zapisane w plikach PDB. Dzięki temu na produkcji jeśli mamy pliki PDB możemy w błędach widzieć ścieżki z góry ustalone a nie te z produkcji, które często są dość długie i nic nie mówiące. Użycie przekładowe może wyglądać w taki sposób:
csc -pathmap:C:\work\tests=\publish t.cs
Ścieżka C:\work\tests jest mapowana na \publish w pliku t.cs.
Podsumowanie
Z C# 7.3 widać wyraźnie, że twórcy C# chcą wydusić z języka jak najwięcej. Chcą ułatwić wydajne programowanie i może też trochę zachęcić do refaktoryzacji kodu pod tym względem. Szczególnie jeśli chodzi o kod nie zarządzany. Najbardziej chyba użytkową zmiana jest dodanie obsługi operatorów == i != dla tuple-i oraz możliwość mapowania ścieżek na inne niż domyślne. Te dwie zmiany często wykorzystuje.
Następny wpis o nowością będzie już z wersji C# 8.0 ale to jeszcze musimy poczekać.