Generics i C#

Tags:    .net c#
Skrevet af Bruger #714 @ 21.03.2006
Med .Net 2.0 indførte Microsoft generics ind i CLIen. Generics giver mulighed for at bruge typestærke funktioner, delegates, klasser osv. uden at kende typen på dem alligevel, og derved undgår man at skulle ind og boxe/unboxe ved at manuelt typecaste til System.Object.

Indhold


1. Forklaring af Generics
2. Kodeeksempler der gør brug af generics
3. Lav egne klasser, metoder osv. der bruger Generics
4. Contraints
5. Funktionel Stack klasse der gør brug af generics

1. Forklaring af Generics


Generics bruges i de situationer hvor man på forhånd ikke kender en eller flere parameter i f.eks. en metode. Her ville man normalt have oprettet en funktion der kunne se sådan ud:

[code="C#"]public void MinMetode(object parameter)
{
//Gør noget med parameter
}
[/code]

Når man så bruger MinMetode ser det hele også fint nok ud, og det virker sådanset også som det skal. Problemet med det er bare, at når man f.eks. giver et string objekt som parameter, så bliver der faktisk foretaget et typecast, altså det samme som at gøre således:

[code="C#"]string minStreng = "ny streng";
MinMetode((object)minStreng);
[/code]

Og her ligger problemet, dette typecast koster utrolig meget performance!

Løsningen på dette problem er, naturligvis, generics. Generics giver mulighed for at fortællle kompileren at man i MinMetode skal bruge parameter og at man ikke ved hvilken type parameter er, det fortæller kalderen af metoden så.

2. Kodeeksempler der gør brug af generics generics



Vi skal nu se på hvordan man kan bruge generics, et godt sted at starte vil være i de collection klasser der følger med i .Net 2.0 som ligger i System.Collections.Generic namespacet. Lad os derfor prøve at oprette en liste, som kun kan indeholde strenge. Dette gøres således:

[code="C#"]using System;
using System.Collections.Generic;
using System.Text;

namespace Generics
{
class Program
{
static void Main(string[] args)
{
//Opret et list objekt der kun kan indeholde strings.
List<string> strengListe = new List<string>();

//Tilføj en streng til listen - bemærk hvordan denne metode bruges ligesom man ville gøre på en ordinær liste
strengListe.Add("min nye streng");

//Man kan ikke tilføje andre typer end strenge til listen!
//strengListe.Add(45); kompiler fejl! Listen kan kun indeholde strenge!

strengListe.Add("min næste streng");

//Løb listen igennem og udskriv dem i konsollen. Bemærk igen hvordan vi bruger listen helt normalt!
for (int i = 0; i < strengListe.Count; i++)
{
Console.WriteLine(strengListe);
}
}
}
}[/code]

Linjen der er det centrale af det hele er linien med:
[code="C#"]List<string> strengListe = new List<string>();
[/code]
Her fortæller vi kompileren at vi kompileren at vores List objekt kun skal indeholde strenge, ved at putte <string> efter selve List objektet. Det er dog vigtigt at lægge mærke til at en List<string> ikke er lig med List<int> dette er to vidt forskellige klasser!

Denne specielle list klasse kan heller ikke sammenlignes med den gamle List klasse, da denne List klasse kun virker med Generics, altså kan du ikke oprette en denne List klasse ligesom man kunne med den gamle:

[code="C#"]using System;
using System.Text;
using System.Collections.Generic;

namespace Generics
{
class Program
{
static void Main(string[] args)
{
//List normalList = new List(); kompiler fejl!
}
}
}[/code]

Dette kunne selvfølgelig godt have ladet sig gjort hvis vi havde skrevet using System.Collections;

En af ulemperne ved Generics er at typedefinitioner hurtigt kan komme til at se klumpet ud, f.eks. hvis vi benytter et Dictionary, som tager 2 ukendte typer (en til nøglerne, og en til værdierne) så ser det hurtigt ret klumpet ud. Her kan vi så benytte using til at lave et alias.

[code="C#"]using System;
using System.Collections.Generic;
using System.Text;
using DictionaryIntString = System.Collections.Generic.Dictionary<int, string>;

namespace Generics {
class Program {
static void Main(string[] args)
{
DictionaryIntString dic = new DictionaryIntString();

//Brug dic
}
[/code]

Som det ses er det rigtig let og bekvemt at bruge Generics. En anden rigtig cool ting ved generics er at du i Visual Studio 2005 (og også Express udgaverne) får fuld intellisense support på generics!

3. Lav egne klasser, metoder osv. der bruger Generics



Når man laver egne klasser, metoder osv der bruger generics, skal man som sagt fortællle kompileren at der er en ukendt type som man ikke ved hvad er. Syntaksen for dette er meget lig med den som man bruger når man benytter klasser, metoder osv. der bruger generics, f.eks. så kan vi definere vores egen CollectionKlasse som er typestærk således:

[code="C#"]using System;
using System.Collections.Generic;
using System.Text;

namespace Generics
{
class MinCollection<T>
{
//Herinde kan vi så bruge T som var det en ganske normal type


//Læg mærke til at vi også sagtens kan oprette arrays med Ter i
protected T[] items;

protected int nextIndex = 0;

protected int capacity;

//Constructors skal IKKE have <T> med!
public MinCollection() : this(10) {}

public MinCollection(int capacity)
{
this.capacity = capacity;
this.items = new T[capacity];
}


//Bemærk her hvordan vi igen bruger T som type
public int Add(T item)
{
this.items[nextIndex] = item;
return nextIndex++;
}

public int Count
{
get
{
return nextIndex;
}
}

//Indexers virker selvfølgelig også fint
public T this[int index]
{
get
{
return items[index];
}
}
}
struct Person
{
public string Name;
public int Age;
}

class Program
{
static void Main(string[] args)
{
//Vi vil nu bruge vores ny-oprettede collection
//Bemærk hvordan vi kan gøre 100% ligesom før!
MinCollection<string> collection = new MinCollection<string>(2);

collection.Add("1. streng");

collection.Add("2. streng");

for(int i = 0; i < collection.Count; i++){
Console.WriteLine(collection);
}

//Man kan selvfølgelig også benytte egne klasser og structs som type parameter
MinCollection<Person> personCollection = new MinCollection<Person>(3);

Person KasperTSW = new Person();

KasperTSW.Name = "Kasper Tanggaard";
KasperTSW.Age = Int32.MaxValue; //KasperTSW er meget gammel!

personCollection.Add(KasperTSW);

Person Far = new Person();

Far.Name = "Far";
Far.Age = 59;

personCollection.Add(Far);

Person Kaare = new Person();

Kaare.Name = "Kaare Hoff Skovgaard";
Kaare.Age = 17;

personCollection.Add(Kaare);

//herefter kan vi så selvfølgelig loope igennem collectionen igen!

}
}
}[/code]

Som det ses er der ikke umiddelbart nogle problemer ved dette.

3. Constraints


Problemerne kommer når vi vil til at udføre operationer inde i vores collection med et item. Grundet at T vi ikke ved noget som helst om hvad det er for en type vi har, så er kompileren nødt til at gå ud fra at de ting vi gør med objekter af typen T ikke kan andet end System.Object. Dvs. vi kan ikke sammenligne 2 T'er med hinanden, vi kan ikke lægge 2 T'er sammen, vi kan faktisk ingenting!

Det er så her at constraints kommer ind i billedet. Dette giver os mulighed for at specificere at T skal implementere interface X, arve fra object Y osv, derudover kan vi også bestemme at T skal indeholde en constructor. Måden dette gøres på er at gøre følgende:

[code="C#"]class MinCollection<T> where T: IComparable<T> {
//En masse kode
}[/code]

Herefter kan vi inde i klassen benytte metoden CompareTo på alle objekter af typen T. For at fortælle kompileren at T skal indeholde en constructor skriver man where T: new()

Har man 2 ubekendte type parameter skriver man:

[code="C#"]class MinCollection<TKey, TValue> where TKey: new(), TValue: IComparable<T> {
//En masse kode
}[/code]

5. Funktionel Stack klasse der gør brug af generics



Her vil jeg vise hvordan en implementation af en simpel, men funktionel stack klasse kunne se ud.

[code="C#"]using System;
using System.Collections.Generic;
using System.Text;

namespace Generics
{
//En stack går efter princippet "Last-In-First-Out" (LIFO) som simpelthen betyder at det sidste item der kom ind, skal være det første til at gå ud.
class MinStack<T>
{
//Indeholder alle vores items
protected T[] items;

//Indeholder indexet på det sidst indsatte item
protected int lastInIndex = -1;

public MinStack() : this(100) { }

public MinStack(int capacity)
{
items = new T[capacity];
}

//Propper et nyt item oven på stacken
public void Push(T item)
{
lock (this)
{ //Sørg for at der ikke sker noget uventet mens vi foretager vores operation

//Ligger en til lastInIndex og derefter bruges denne værdi i arrayet
items[++lastInIndex] = item;
}
}

//tager det øverste item af stacken
public T Pop()
{
lock (this) //Igen ingen må pille mens vi arbejder
{
//Returner det sidste item der røg ind, og træk 1 fra lastInIndex;

T item = items[lastInIndex];

items[lastInIndex--] = default(T); //Vi benytter her default keywordet som giver os standart værdien for T

return item;
}
}
//Simpel property
public int Count
{
get
{
lock (this) //Better safe than sorry
{
return lastInIndex + 1;
}
}

}
}
class Program
{
static void Main(string[] args)
{
MinStack<int> stack = new MinStack<int>(5);

stack.Push(1);
stack.Push(2);
stack.Push(3);

int popped = stack.Pop(); //Fjerner 3
stack.Push(4);
stack.Push(5);


//Kør sålænge der er items i stacken
while (stack.Count != 0)
{
int item = stack.Pop();

Console.WriteLine(item.ToString());
}
Console.WriteLine(popped.ToString()); //Udskriver 3
Console.ReadLine();


}
}
}[/code]

Og så har vi så en stack som vi kan bruge med de typer som vi vil (bemærk dog at der allerede er defineret en generisk stack klasse i System.Collections.Generic).

Det var det, jeg håber at generics kan hjælpe dig, så du er fri for at skrive samme kode igen og igen for at få typestærke collections, eller hvad det nu lige må være du står og mangler, hvor generics kan hjælpe dig!


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (8)

User
Bruger #2730 @ 22.03.06 07:58
God artikel. Godt arbejde, dejligt endeligt at se nogle nye artikler i .NET generen, og så endda i 2.0. Sweet :-)
User
Bruger #2730 @ 22.03.06 07:59
*genren
User
Bruger #1 @ 22.03.06 21:04
Yeah, lad os få lidt mere af den slags! :)
User
Bruger #714 @ 22.03.06 22:26
Tak for de fine kommentarer.

Jeg vil da os helt vildt gerne skrive flere, har dog ikke umiddelbart nogle ideér til en anden artikel men det kommer nok. Derudover skal jeg måske også lige få aflastet Kasper lidt mere ved indsendelse af næste artikel ;) (Han ved hvad jeg mener).

Men ideér er da altid velkomne og helt klart det samme med kommentarerne så jeg ved hvad der kan/skal gøres bedre.
User
Bruger #714 @ 04.04.06 21:48
Hov en lille rettelse.

Jeg kan se at jeg har skrevet at der findes en klasse der hedder List i System.Collection namespacet, dette er ikke korrekte, den klasse der opfører sig meget lig med en generisk list klasse hedder ArrayList.
User
Bruger #6653 @ 23.01.07 10:11
Wow, du holder dig godt af din alder, Kasper ;)
User
Bruger #8985 @ 10.09.11 21:57
Er det bare mig, eller er det lidt synd at kodeeksemplerne ikke står i rigtige C#-kodebokse? Jeg ved ikke om forfatteren selv kan gå ind og rette i artiklen, men ellers burde en admin eller Kasper da lige bruge 5 minutter på det.
User
Bruger #714 @ 11.09.11 03:31
Nu skal jeg ikke kunne udlukke at muligheden er der, men jeg kan i hvert fald ikke finde den. Men nej Thomas, jeg er helt enig.
Du skal være logget ind for at skrive en kommentar.
t