Pored “velikih” novih mogućnosti C# 9 postoji i niz novih mogućnosti koje nisu pod svijetlima reflektora, ali će nam biti korisne. Ovdje ćemo pogledati pet “malih” novih mogućnosti koje će pomoći programerima u njihovom radu.
Covariant povratni tipovi (covariant return types)
Overridden metode, za koje vidim da se koristi prijevod nadjačane metode, pa ću ga koristiti i ovdje, postoje od prve verzije C#. Sada C# 9 donosi novost koja će nam omogućiti veću fleksibilnost kada ih koristimo. Covariant povratni tipovi (covariant return types) je nova mogućnost C# koja omogućuje pisanje nadjačane metode (overridden method) u izvedenoj klasi koja ima različiti povratni tip u odnosu na originalnu metodu u baznoj klasi. Pri tome uvjet je da su i povratni tipovi hijerarhijski povezani nasljeđivanjem.
Pogledajmo ovu novu mogućnost C# na sljedećem primjeru:
Radimo na aplikaciji koja sadrži baznu klasu Vehicle i dvije izvedene klase Bike i Car. Za svaku od ove tri klase treba generirati drugačiji izvještaj. Postoji i odgovarajuća hijerarhija klasa za izvještaje: bazna klasa VehicleReport i izvedene klase BikeReport i CarReport.
Hijerarhije klasa domenskih entiteta i klasa za izvještaje u ovom primjeru
Želimo imati istoimenu metodu GenerateReport() u baznoj klasi Vehicle i istoimene nadjačane metode (overridden methods) u izvedenim klasama Bike i Car, ali svaka od ovih metoda treba imati različiti povratni tip. U baznoj klasi Vehicle metoda GenerateReport() treba vraćati objekt klase VehicleReport. U izvedenoj klasi Bike metoda GenerateReport() treba vraćati BikeReport. A, sukladno tome, u klasi Car treba postojati metoda GenerateReport() koja vraća CarReport. Pri tome sve klase za izvještaje (*Report) su međusobno povezane hijerarhijom nasljeđivanja kao što je prikazano na prethodnoj slici.
Do sada ovo nije bilo moguće – nadjačane metode (overridden methods) u izvedenoj klasi morale su imati isti povratni tip kao i originalne metode u baznoj klasi.
Ali, počevši od C# 9 ovo je postalo moguće: covariant return types u C# 9 omogućuju nam da promijenimo povratni tip metode kada ju nadjačamo u izvedenoj klasi.
public class Vehicle { public virtual VehicleReport GenerateReport() { VehicleReport report = new VehicleReport(); //... return report; } //... } public class Car : Vehicle { public override CarReport GenerateReport() { CarReport report = new CarReport(); //... return report; } //... }
Ova nova mogućnost C# 9 omogućuje nam da implementiramo svoje ideje, ali bez korištenja zaobilaznih načina koji nemaju jednostavnost i eleganciju covariant return types. Tu mislim na zaobilazne načine poput davanja drugačijih imena metodama u izvedenim klasama u odnosu na originalnu metodu u baznoj klasi. Ili pretvaranja povratnih tipova koristeći cast operator.
Pojednostavljeno kreiranja novih objekata s target-typed new expressions
U prvim verzijama C# kada smo kreirali novi objekt neke klase morali smo napisati ime klase dva puta, jednom sa svake strane znaka “=”. Na primjer, kada smo kreirali novi objekt klase Car, morali smo dva puta napisati “Car”, po jednom sa svake strane znaka jednakosti:
Car car = new Car();
C# 9 nam donosi target-typed new expressions: mogućnost da ime klase Car napišemo samo jednom, na početku linije:
Car car = new();
U slučaju da konstruktor prima parametre samo trebamo napisati iza ključne riječi new te parametre unutar zagrada:
Car car = new(manufacturer, model);
U čemu je razlika između ključne riječi var i target-typed new expressions?
Kada sam vidio target-typed new expressions palo mi je na pamet pitanje: zašto ne bi smo koristili ključnu riječ var, koja u C# postoji već duže vrijeme, da bi ostvarili isti cilj: ne ponavljali ime klase dva puta kada kreiramo novi objekt?
Pokazalo se da je target-typed new expression moćniji nego ključna riječ var.
Target-typed new expressions može se koristiti na mjestima gdje se ne može koristiti ključna riječ var: u slučaju property-ja i polja u klasi (class fields) ključna riječ var ne može se koristiti, ali target-typed new expressions će i na tim mjestima pomoći nam da ne pišemo dva puta ime klase.
Poboljšanje ternarnog operator (Target-typed conditional expressions)
Ternarni operator ?: (ternary operator ?:) omogućuje nam da na jezgrovit način zapišemo izbor između dvije opcije: target = condition ? optionA : optionB
U prethodnim verzijama C# morala je postojati konverzija tipova s jedne strane “:” na drugu stranu – drugim riječima, konverzija između optionA i optionB.
Nastavimo s našim primjerom o vozilima. Prije C# 9 nismo mogli imati objekt klase Car s jedne strane “:” i objekt klase Bike s druge strane, bez da smo koristili cast operator za eksplicitnu konverziju. Trebali smo cast operator jer, iako svaki od tih dva objekta se može implicitno konvertirati u njihovu zajednički baznu klasu Vehicle, objekti klasa Car i Bike se ne mogu konvertirati jedan u drugoga.
Krenimo od starijeg C# koda – prije C# 9 trebali smo cast operator (Vehicle) dodati prije objekata car i bike:
Vehicle selectedVehicle = isRaining ? (Vehicle)car : (Vehicle)bike;
U C# 9, zahvaljujući target-typed conditional expressions, možemo imati objekte različitih tipova sa svake strane “:”, pod uvjetom da oba objekta se mogu konvertirati u ciljani tip s lijeve strane znaka jednakosti (klasu Vehicle u našem slučaju).
Zahvaljujući tome u C# 9 možemo prethodnu liniju koda napisati jednostavnije:
Vehicle selectedVehicle = isRaining ? car : bike;
U ovom primjeru oba tipa Car i Bike mogu se implicitno konvertirati u tip Vehicle koji se nalazi lijevo od znaka jednakosti.
Pada mi napamet pitanje: što ako napišemo ključnu riječ var kada definiramo varijablu selectedVehicle, umjesto da eksplicitno definiramo tip te varijable? Ovo će uzrokovati grešku.
Metoda za inicijalizaciju modula (Module initializer)
Module initializer je metoda koja će se pozvati kada se modul (assembly) učita. Ovu metodu će pozvati runtime prije bilo koje druge metode u cijelom modulu, uključujući i metodu Main(). Koja metoda je module initializer određuje se atributom [ModuleInitializer].
class AssemblyInitializer { [ModuleInitializer] internal static void Init() { // Runs even before // static void Main() in class Program. //... } }
Metoda može biti module initializer ako zadovoljava sljedeće kriterije:
- metoda je statička
- metoda nema ulazne parametre
- povratni tip je void
- metodi se može pristupiti iz modula u kojemu se nalazi – drugim riječima, i metoda i klasa u kojoj se ona nalazi moraju imati internal ili public Prema tome lokalna funkcija ne može biti module initializer.
- nije generička metoda
- metoda se ne nalazi unutar generičke klase
Atributi za lokalne funkcije
U C# 9 možemo staviti atribute iznad lokalnih funkcija.
Pogledajmo ovo na sljedećem jednostavnom primjeru: možemo koristiti atribut [MethodImpl] s parametrom MethodImplOptions.AggressiveInlining iznad lokalne funkcije CalculateYearsWithCompany. Ova lokalna funkcija je smještena unutar metode CalculateBonus koja računa bonus na temelju, između ostaloga, broja godina koje je zaposlenik proveo u kompaniji.
public int CalculateBonus(Employee employee) { int yearWithCompany = CalculateYearsWithCompany(employee); //... [MethodImpl(MethodImplOptions.AggressiveInlining)] int CalculateYearsWithCompany(Employee employee) { //... } }
Hoćemo li sa C# 9 pisati jednostavniji kod?
Sada u C# 9 nadjačana (overridden) metoda u izvedenoj klasi može imati drugačiji povratni tip od metode u baznoj klasi, zahvaljujući covariant return types. Ova nova mogućnost C# će omogućiti programerima jednostavnije izraziti svoje zamisli i učiniti će kod lakšim za čitanje. Obratite pažnju da ti povratni tipovi moraju biti povezani pomoću nasljeđivanja – povratni tip nadjačane metode u izvedenoj klasi mora naslijediti povratni tip metode u osnovnoj klasi.
Ako nam treba metoda koja će se prva pozvati kada se modul učita, možemo napisati module initializer.
Target-typed new expression omogućuje nam da napišemo ime tipa samo jednom kada stvaramo novi objekt. Dodatno, ne samo da target-typed new expression možemo koristiti kod lokalnih varijabli nego, za razliku od ključne riječi var, možemo ga koristiti i kod property-ja i polja u klasi (class fields).
Target-typed conditional expression pojednostavljuje pravila za ternarni operator ?: tako da možemo staviti objekte različitih klasa sa svake strane “:” u ternarnom operatoru, bez korištenja cast operatora. Uvjet je da se svaki od ta dva objekta može implicitno konvertirati u isti tip kojeg je i varijabla lijevo od znaka jednakosti, u koju će se pohraniti rezultat.
Kada nam zatrebaju atributi u slučaju lokalnih funkcija sada, počevši od C# 9, možemo ih koristiti.
Iako je ovih pet novih mogućnosti C# 9 manje poznato i o njima se manje pričalo, siguran sam da će se u praksi često koristiti. Razlozi zašto to smatram su to što nam omogućuju da pišemo jednostavniji kod i lakše izrazimo svoje zamisli.
- Najvažnije novosti u C# 10 - 09.11.2021.
- Manje poznate mogućnosti C# 9 - 18.06.2021.
- Pattern matching u C# 9 – korak u pravom smjeru - 05.03.2021.