C# generički tipovi: povećan integritet tipova i bolja iskorištenost koda

Autor teksta je Boško Bezik, freelance softver developer i entuzijasta .NET ekosistema. Uglavnom se bavi razvojem ASP.NET veb aplikacija i Xamarin mobilnih aplikacija, ali i drugim oblastima kao što je npr. game development.

Boško Bezik
17/12/2019

Ukoliko ste radili sa kolekcijama u C#-u, sigurno ste primetili da većina klasa za kolekcije ima dodatni parametar (ili parametre) označen <> strelicama. Ovo je tzv. generički parametar tipa ili samo generički parametar. On predstavlja tip vrednosti koji će se skladištiti u kolekciji. Takođe je moguće imati više generičkih parametara, npr. Dictionary <TKey, TValue> ima dva generička parametara, jedan za ključ i jedan za vrednost. Svi tipovi koji imaju mogućnost zadavanja generičkih parametara se zovu generički tipovi (eng. generics).

Da li su generički tipovi oduvek bili u C#-u?

Naime ono što mnogi ne znaju jeste da generički tipovi nisu bili prisutni u C#-u od pojave jezika, već su dodati naknadno u C# 2.0 izdanju. Dosta se spekuliše koliko bi C# bio bolji (ili gori) da su inženjeri imali strpljenja i dodali generičke tipove u prvom izdanju, ali izgleda da nikada nećemo saznati.

Bez obzira na to, C# je postigao veliki uspeh. U .NET-u većinu generičkih tipova čine kolekcije koje se nalaze u System.Collections.Generic namespace-u. Pre C# 2.0 korišćen je System.Collections namespace koji je sadržao obične kolekcije kao što je ArrayList. Možda ćete negde videti da neko koristi ArrayList kolekciju, ali to je uglavnom zbog kompatibilnosti sa starijim kodom. C# dokumentacija preporučuje da se koriste generičke kolekcije iz razloga koje ćemo sada videti.

Povećan integritet tipova (eng. type safety)

Generički tipovi nisu tu samo da nam olakšaju rad sa kolekcijama i očiste kod, već uz njih dolaze drugi benefiti. Počnimo sa povećanim integritetom tipova (eng. type safety) podataka tako što ćemo ukratko da razjasnimo šta je to.

Integritet tipa predstavlja karakteristiku koda koja garantuje da će neka vrednost biti određenog tipa i da programer može biti siguran da taj kod neće izazvati neočekivano ponašanje programa. Kada koristimo staru ArrayList kolekciju, tokom povlačenja vrednosti iz nje moramo se pobrinuti da ih konvertujemo u odgovarajući tip, jer su sve vrednosti unutar te kolekcije objekti.

Ovo predstavlja problem jer sada imamo mogućnost da unesemo vrednosti drugog tipa koje ne želimo, što može da izazove izuzetke (eng. exceptions) tokom rada programa. Takođe, moramo da pišemo dodatni kod za proveru tipa vrednosti. Naime, nevolje sa ArrayList ne staju samo tu. Svaka konverzija tipa iz objekta u željeni tip loše utiče na performanse programa, što postaje očiglednije kako koristimo više kolekcija. Sa generičkim kolekcijama ne postoji mogućnost unosa pogrešnog tipa vrednosti jer generička kolekcija ima generički parametar koji garantuje da će kolekcija sadržati samo tip koji smo zadali (sa <> strelicama). Ukoliko pokušamo da unesemo tip koji ne odgovara, dobijamo grešku tokom kompilacije programa.

Bolja iskorištenost koda (eng. code reuse)

Pored navedenih generičkih kolekcija, možemo da pravimo i sopstvene generičke tipove. Ovo nam omogućava da znatno povećamo efikasnost napisanog koda. Uzmimo primer sledeće klase koja nije generički tip:

public class Identitet
{
    public int Id { get; set; }
    public List<Uloge> Uloge { get; set; }
    public List<Privilegije> Privilegije { get; set; }

}

Obratite pažnju da naša klasa Identitet ima svojstvo Id tipa int. Šta ako nam se pojavi potreba da negde koristimo tip Identitet koji mora da ima svojstvo Id tipa string ili možda GUID? Očigledno je da bismo morali da napravimo nove klase IdentitetString i IdentitetGuid. Međutim, ovo je veoma nepoželjno jer mi sada pravimo nove klase samo zbog jednog svojstva, i još gore, kada možda odlučimo da napravimo promene na klasi Identitet, moramo da se setimo da napravimo iste promene i na IdentitetString i IdentitetGuid. Da ne bismo to radili, najbolje rešenje jeste da napravimo samo jednu generičku klasu Identitet<T>.

public class Identitet<T>
{
    public T Id { get; set; }
    public List<Uloge> Uloge { get; set; }
    public List<Privilegije> Privilegije { get; set; }
}

Sada imamo samo jednu klasu i možemo da joj po želji zadajemo generički tip koji će da koristi unutar sebe. Ukoliko odlučimo da pravimo izmene na klasi, to radimo samo na jednom mestu. Odnosno, dobili smo znatno više fleksibilnosti sa znatno manje koda.

Postavljanje ograničenja generičkih tipova

Kao šlag na tortu, generički tipovi nam omogućavaju da deklarišemo ograničenja na vrste tipova koji se mogu zadati. Recimo da za našu Identitet klasu želimo da se mogu koristiti samo struct-ovi kao generički parametri kao što su: int, long, string i Guid. Ovo je veoma lagano sa generičkim tipovima.

public class Identitet<T> where T : struct
{
    public T Id { get; set; }
    public List<Uloge> Uloge { get; set; }
    public List<Privilegije> Privilegije { get; set; }
}

Sada, ukoliko pokušamo da napravimo instancu klase Identitet<T> sa generičkim parametrom tipa koji nije podržan (u ovom slučaju neku klasu ili interfejs), dobićemo grešku tokom kompilacije. Ograničenja koja možemo staviti za generičke parametre su:

where T : struct  // Svi vrijednosni tipovi koji nemogu biti `null`

where T : class  // Svi referenti tipovi (klase, interfejsi, delegati ili nizovi)

where T : notnull  // (Dodano u C# 8.0) Svi vrijednosni i referentni tipovi koji 
                   // nemogu biti `null`

where T : unmanaged  // Svi `unmanaged` tipovi (pogledati dokumentaciju)

where T : new()  // Svi tipovi sa `public` konstruktorom bez parametara

where T : <ime bazne klase>  // Svi tipovi koji su ili nasleđuju datu klasu

where T : <ime interfejsa>  // Svi tipovi koji implemetišu dati interfejs

where T : U  // Svi tipovi koji nasleđuju generički tip `U`

Detaljan opis ovih ograničenja možete pregledati u C# dokumentaciji.

Zaključak

Kao što smo videli kroz nekoliko primera koda, generički tipovi su zaista jedno od onih retkih svojstava jezika koja nemaju negativnih strana. Obezbeđuju nam bolji integritet tipova i uklanjaju svaku mogućnost da zadamo pogrešan tip vrednosti nekom generičkom tipu. Poboljšavaju performanse programa tako što uklanjaju konverzije tipova iz kolekcija i omogućavaju nam da ograničimo generičke parametre tipova koji se mogu zadati.


Autor teksta je Boško Bezik, freelance softver developer i entuzijasta .NET ekosistema. Uglavnom se bavi razvojem ASP.NET veb aplikacija i Xamarin mobilnih aplikacija, ali i drugim oblastima kao što je game development. U slobodno vreme deli svoje znanje i iskustvo na društvenim mrežama. Tekst koji ste čitali originalno je objavljen na LinkedIn-u.

Boško Bezik

Objavio/la članak.

utorak, 17. Decembar, 2019.

IT Industrija

🔥 Najčitanije