Człowiek się tak szybko starzeje, że nie zauważa kolejnych wersji frameworków przyzwyczajając się do znanych konceptów. Co prawda od daty wypuszczenia wersji 8 .NETu i C# 12 minęło już trochę czasu spróbujmy spojrzeć z tej perspektywy na nowe koncepty i możliwości oraz ich wpływ na codzienną pracę w formie case-study: analiza i komentarz w czasie.
Przypadek #1: primary constructor
To jest coś do czego nie byłem przekonany od samego początku w dużych projektach, do których jestem przyzwyczajony. Zasada jest prosta: dodajmy cukier składniowy tak, aby móc pisać mniej tekstu a osiągnąć to samo. To jest ostatnio domena nowych wersji.
Przykład z dokumentacji wydaje się całkiem przyjemny:
public class SavingsAccount(string accountID, string owner, decimal interestRate)
: BankAccount(accountID, owner)
{
public SavingsAccount() : this("default", "default", 0.01m) { }
//...
}
Ot przenosimy zapis konstruktora do definicji klasy. Minusem jest konieczność wykorzystywania tego konstruktora przez inne (zastępuje wywołanie `: base(`). Jeden sposób kreacji obiektu to raczej oczekiwana cecha niż wyjątek.
Dokumentacja (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/primary-constructors)
Przypadek #2: Getter vs expression body definition
Ten przypadek akurat dla mnie wydaje się oczywisty, ale dla nieco starszych programistów wydawał się dziwny, a mianowicie możliwość zapisu gettera dla pól readonly przez expression body definition.
public class Test {
private string _testFieldValue;
public string TestField1 {
get { return _testFieldValue; }
}
public string TestField2 { get => _testFieldValue; }
public string TestField3 => _testFieldValue;
}
Wszystkie te pola mają takie samo zachowanie dla użytkownika klasy Test. Osobiście lubię wykorzystywać ostatni przypadek dla pól, które mają konwertować wartość innego pola w obiektach domenowych, np. konwersja GUIDa do stringa w wybranym formacie.
Przypadek #3: Collection expressions
Czasem mam wrażenie, że C# i Python się do siebie zbliżają. Jakże inaczej byłoby wytłumaczyć taki sam sposób inicjalizacji kolekcji (przynajmniej dla zmiennych tablicowych) w obu językach?
List<int> numbers = [7, 8, 9];
Co ciekawe nie można używać collection expression w celu dostarczania danych, których wartość jest oczekiwana w momencie kompilacji, np. inicjalizacja stałych lub domyślna wartość dla metody, ale tak samo nie można w tym miejscu zainicjalizować obiektów implementujących IEnumerable np. List więc nie dziwi to bardzo.
Dodatkowo można wykorzystać ten sposób do weryfikacji sekwencji elementów w liście lub obiekcie tablicowym również z wykorzystaniem znaków typu wildcard oraz do nadawania wartości zgodnie z pozycją w kolekcji.
List<int> numbers = [7, 8, 9];
Assert.That(numbers is [7, 8, 9]);
List<int> numbers = [7, 8, 9];
Assert.That(numbers is [7, _, 9]);
List<string> fruits = ["apple", "banana", "strawberry"];
if (fruits is [var first, var second]) {
Assert.That(first, Is.EqualTo("apple");
Assert.That(second, Is.EqualTo("banana");
}
W kwestii oceny to znowu jest kwestia przyzwyczajenia. Dla mnie nowy sposób jest powrotem do korzeni i dobrych wspomnień z językiem Python, który swą łatwością i prostotą wtedy przyciągał.
Dokumentacja (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions)
Przypadek #4: Globalizacja definicji
Pozwolę sobie cofnąć się do wersji 10tej C# i spojrzeć na zmiany, które przeszły niezauważone, m. in. możliwość definiowania namespace dla pliku w formie deklaracji zamiast bloku. Ta zmiana spowodowała głównie zmniejszenie ilości znaków w pliku i zwiększenie czytelności kodu.
namespace MyNamespace;
Dokumentacja (https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#file-scoped-namespace-declaration)
Drugą sprawą, której również przyświecały te same cele jest "global usings". Przyznam szczerze, że wykorzystuję ją masowo w testach zapominając kompletnie o konieczności importowania biblioteki do testów.
global using NUnit.Framework;
Dokumentacja (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive#global-modifier)
Kolejną wprowadzoną niedawno zmianą jest centralne zarządzanie wersjami paczek, które znacząco przyspieszyło w dużych projektach proces weryfikacji poprawności tzw. pull requestów. Aktualizacja zależności, która jest wykorzystywana w wielu projektach teraz wymaga zmiany jednej linijki w pliku z wersjami paczek.
Dokumentacja (https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management)
Gdzie nas to zaprowadzi?
Skracanie pojęć i słów jest widoczne w codzienności od lat. Czy to oznacza, że kiedyś będziemy mówić kodami kreskowymi?
Pewnie nie, ponieważ ludzki mózg potrzebuje porównań i obrazów, które może połączyć w całość. Mała część społeczeństwa ma dobrą pamięć do numerów. Możemy też postępować jak luddyści, technologiczni hipsterzy XIX wieku – zawsze przed trendem, ale w odwrotną stronę. Gdyby żyli dziś, zamiast rzucać butami w maszyny parowe, pewnie wypowiadaliby wojnę automatom do kawy i smartfonom, wykrzykując: "Nie będziesz mojej pracy zabierać, robocie!".
Tak czy owak zmiany są dobre, pod warunkiem, że je dobrze wykorzystamy. Celem życia jest adaptacja i przetrwanie.







