Julekalender 2004 om Whidbey


24. dec 2004 23:41

I version 2 af .NET frameworket er der kommet mulighed for at benytte såkaldte generiske typer. Generiske typer fungerer på designtidspunktet som type-placeholdere; placeholdere som først på runtidspunktet fastlægges konkret. Det giver både fleksibilitet og en høj grad af typestærk opførsel - også på designtidspunktet. For at beskrive hvordan generiske typer virker, vil vi tage udgangspunkt i følgende meget simple maksimerings-funktion:

Public Function MaxIntegers(ByVal i1 As Integer, ByVal i2 As Integer) As Integer

  If i1 > i2 Then
    Return i1
  Else
    Return i2
  End If

End Function
public int MaxIntegers(int i1, int i2)
{
  if (i1 > i2)
  {
    return i1;
  }
  else
  {
    return i2;
  }
}

Denne funktion er naturligvis typestærk med de fordele, det giver i form af intellisense-support og type-check på kompileringstidspunktet - til gengæld er funktionen også begrænset til kun at kunne arbejde med integers. Hvis vi ønsker, at lave en funktion, der kan finde maksimum af f.eks. strenge har vi indtil videre været henvist til enten at lave en ny version af Max-funktionen specifikt til strenge eller at nøjes med en typesvag/typeløs implementation. Den umidelbare ændring af MaxIntegers til en typeløs version baseret på Object-datatypen ville være:

Public Function MaxObject(ByVal i1 As Object, ByVal i2 As Object) As Object

  If i1 > i2 Then
    Return i1
  Else
    Return i2
  End If

End Function
public object MaxObject(object i1, object i2)
{
  if (i1 > i2)
  {
    return i1;
  }
  else
  {
    return i2;
  }
}

Denne funktion kan dog ikke kompileres, da man ikke kan sammenligne instanser af Object-datatypen med > (større end) operatoren. I stedet kan vi ændre Max-funktionen til en lidt mere typestærk version baseret på System.IComparable:

Public Function MaxIComparable(ByVal c1 As System.IComparable, ByVal c2 As System.IComparable) As Object

  If c1.CompareTo(c2) > 0 Then     'Giver en run-time fejl
    Return c1
  Else
    Return c2
  End If

End Function
public object MaxIComparable(System.IComparable c1, System.IComparable c2)
{
  if (c1.CompareTo(c2) > 0)        //Giver en run-time fejl
  {
    return c1;
  }
  else
  {
    return c2;
  }
}

Et problem med denne implementation er dog, at kompileren vil acceptere følgende, som dog naturligvis vil give en run-time fejl, idet strenge og integers ikke kan sammenlignes baseret på System.IComparable-interfacet.

Dim s As String = "Hello"
Dim t As Integer = 42

MaxIComparable(s, t)
string s = "Hello";
int t = 42;

MaxIComparable(s, t);

En version baseret på generics kunne se ud som følger:

Public Function Max(Of T As System.IComparable)(ByVal i1 As T, ByVal i2 As T) As T

  If i1.CompareTo(i2) > 0 Then
    Return i1
  Else
    Return i2
  End If

End Function
public T Max(T i1, T i2) where T : System.IComparable
{
  if (i1.CompareTo(i2) > 0)
  {
    return i1;
  }
  else
  {
    return i2;
  }
}

T er her en placeholder for en mere konkret type. - en placeholder der dels sørger for at i1 og i2 er af samme type dels sikrer, at den konkrete type implementerer System.IComparable. Den konkrete type, som T skal erstattes med, defineres ved brugen af funktionen. Så ønskes en version af Max-funktionen, som kan finde maksimum for to integers, vil kaldet se ud som følger:

Dim i1 As Integer = 17
Dim i2 As Integer = 42

Max(Of Integer)(i1, i2)
int i1 = 17;
int i2 = 42;

Max<int>(i1, i2);

Efter at T er fastlagt til i dette konkrete tilfælde at være en placeholder for Integer-typen, vil brugen af Max-funktionen opføre sig fuldstændig som MaxIntegers-funktionen - der vil atltså være fuld intellisense og typestærke kompileringscheck. Forsøges Max-funktionen kaldt med en streng og en integer som parametre som i følgende eksempel, vil man få en kompileringsfejl, idet definitionen af Max foreskriver, at begge de to parametre (såvel som iøvrigt retur-værdien) skal være af samme type - i dette kald integers.

Dim s As String = "Hello"
Dim t As Integer = 42

Max(Of Integer)(s, t)
string s = "Hello";
int t = 42;

Max(Of Integer)(s, t)

Generiske typer er altså nyttige, hvis man ønsker at lave generel anvendelig kode uden at gå på kompromis med intellisense-understøttelse og typestærke kompileringschecks.

Generiske typer anvendes ikke bare i forbindelse med enkeltstående funktioner som i ovenstående eksempel, men også - og nok oftest - i forbindelse med collections, hvor man ofte ønsker at gennemtvinge typestærk opførsel på samme måde som i ovenstående simple eksempel, men nu ikke begrænset til en enkelt funktion men for alle collectionens metoder. En ofte benyttet collection-datatype er System.Collections.ArrayList, som der her ses et simpelt eksempel på brugen af:

Dim typeLessList As New System.Collections.ArrayList

typeLessList.Add(17)
typeLessList.Add(42)
typeLessList.Add("Whidbey")

Dim i As Integer = CInt(typeLessList(0))
System.Collections.ArrayList typeLessList = new System.Collections.ArrayList();

typeLessList.Add(17);
typeLessList.Add(42);
typeLessList.Add("Whidbey");

int i = (int)(typeLessList[0]);

Bemærk at System.Collections.ArrayList er en typeløs collection, der accepterer alle datatyper (objekter). Man vil altså som i ovenstående eksempel kunne tilføje såvel integers som strenge til collectionen. Endvidere vil man ofte skulle typecaste objekterne til en mere konkret datatype ved udtagning af collectionens elementer - med mindre man da har brug elementerne som rene objekter.

Version 2 af .NET frameworket definerer i System.Collections.Generic-namespacet en række generiske collections, som er klar til brug. Man kan naturligvis definere sine egne typestærke collections, men oftest vil de allerede definerede givet vis være tilstrækkelige. Et eksempel på en sådan predefineret generisk collection er System.Collections.Generic.List. Eksemplet med ovenstående arrayliste vil med brug af den generiske List-collection tage sig ud som følger:

Dim genericList As New System.Collections.Generic.List(Of Integer)

genericList.Add(17)
genericList.Add(42)

Dim j As Integer = genericList(0)
System.Collections.Generic.List<int> genericList = new System.Collections.Generic.List<int>();

genericList.Add(17);
genericList.Add(42);

int j = genericList[0];

Der er som tidligere bemærket fuld intellisense-understøttelse i brugen af genericList-collectionen, og den opfører sig lige så typestærkt som en gammeldags typestærk collection: Hvis man søger at tilføje en streng til collectionen fås en kompileringsfejl, og man behøver ikke typecaste elementerne til integers, idet anvendelsen af generiske typer sikrer, at det er integers, der returneres.

Der er mange tekniske og anvendelsesmæssige forhold, som det er interessant at beskæftige sig med i forbindelse med generiske typer. Disse forhold vil vi se nærmere på i kommende dybdegående artikler.



Abonnér på mit RSS feed.   Læs også de øvrige indlæg i denne Blog.