Sinds .NET2.0 kunnen wij VB programmeurs gebruik maken van een techniek die 'Generics' heet. 'Generic' vertaald naar het Nederlands is 'algemeen', en dit dekt de lading aardig.
In mijn vorige blog over collecties heb ik al iets laten zien van Generics. Eerst een kleine opfrisser.

  1: Dim teksten As New List(Of String)
  2: teksten.Add("Hallo")
  3: teksten.Add("hoe zat")
  4: teksten.Add("dit ook alweer?")
  5: ' Onderstaande mag niet, omdat 42 geen String is.
  6: teksten.Add(42)
  7: 
  8: Dim bericht As String = teksten(2)

U ziet dat de T in List(Of T) vervangen kan worden door een type, in dit voorbeeld String, waarna er enkel nog maar Strings in de List mogen. In plaats van String had ik ook Integer, Boolean, Exception of Persoon kunnen zeggen. Of één van de vele honderden andere klassen in het .NET Framework of die u wellicht zelf heeft gemaakt.
Nu kunnen wij natuurlijk ook zelf generieke klasses maken.
Dit is zelfs erg makkelijk! Door simpelweg (Of T) achter een klassenaam te plakken wordt deze generic.

 

  1: Public Class Voorbeeld(Of T)
  2:    Public Property Waarde Of T
  3: End Class
  4: 
  5: ' Gebruik:
  6: Dim v1 As New Voorbeeld(Of String)
  7: v1.Waarde = "Dit is makkelijk!"
  8: ' Overschrijf de waarde.
  9: v1.Waarde = "Nog een waarde..."
 10: ' Het volgende mag dus niet.
 11: v1.Waarde = 42
 12: 
 13: Dim v2 As New Voorbeeld(Of Integer)
 14: v2.Waarde = 42
 15: ' Overschrijf de waarde.
 16: v2.Waarde = 99
 17: ' Het volgende mag dus niet.
 18: v2.Waarde = "Foutje..."

U ziet dat T vervangen wordt met het type wat u zelf aangeeft bij de declaratie van het Object.
U kunt ook makkelijk meerdere generics gebruiken:

  1: ' U kunt een hele hoop generics gebruiken.
  2: Public Class Voorbeeld(Of T1, T2, T3)
  3:    Public Function DoeIets(ByVal waarde1 As T1, ByVal waarde2 As T2) As T3
  4:       ' Doe hier iets...
  5:    End Function
  6: End Class

U ziet dat u de generieke typen overal in kunt zetten. Ook als parameters aan een functie. Bovenstaande voorbeeld is in de meeste gevallen overigens vrij nutteloos omdat u van tevoren niet weet wat T1, T2 en T3 zijn. Hoe zou een input van T1 en T2 dus tot een T3 kunnen leiden?
Hiervoor is ook iets bedacht. U kunt namelijk een restrictie aan een generic opleggen. Dit is net zo makkelijk.

 

  1: Public Class RestrictieVoorbeeld(Of T As IComparable)
  2:    Public Functie Vergelijk(ByVal comparable1 As T, ByVal comparable2 As T) As Integer
  3:       Return comparable1.CompareTo(comparable2)
  4:    End Function
  5: End Class

IComparable is een Interface. Een Interface is, grof gezegd, een 'blueprint' van een klasse. Een klasse kan een Interface implementeren en zegt daarmee 'ik voldoe aan deze blueprint'. Een klasse die IComparable implementeert heeft de functie CompareTo die een Object als input parameter verwacht en een Integer als resultaat terug geeft.
In dit voorbeeld weet u niet wat T is behalve dat het een IComparable is (String, Integer, Decimal, Boolean... zijn allemaal IComparables). Iedere IComparable heeft een CompareTo functie (deze staat immers in de Interface gedefiniëerd). Deze kunt u nu dus gebruiken, ongeacht wat T is.
Overigens is er van de Interface IComparable ook een generieke variant, IComparable(Of T). Een String implementeert bijvoorbeeld deze Interface. De String is dan ook een IComparable(Of String). U zou uw klasse dus ook als volgt kunnen definiëren.

 

  1: Public Class ComparableVoorbeeld(Of T As IComparable(Of T))

Als u nu T zou vervangen door String dan zou String dus een IComparable(Of String) moeten zijn, en dit is ook het geval.

We hebben tot nu toe gekeken naar Generics op klasse niveau. Het is echter ook mogelijk om Generics op Method niveau toe te passen. Dit werkt nagenoeg hetzelfde.
 

  1: Public Function DoeIets(Of T)(ByVal waarde As T) As String
  2:    Return waarde.ToString
  3: End Function

Dit is natuurlijk een voorbeeld waar u weinig mee kan. Maar, let u eens op het gebruik van een dergelijke functie...
 

  1: Dim bool As Boolean = True
  2: ' Roep de generieke functie aan.
  3: Dim tekst As String = DoeIets(bool)
  4: ' tekst heeft nu de waarde 'True'.

Hey, waar is nu die (Of String) gebleven!? .NET kan het type van T voor u herleiden. De compiler weet dat u een Boolean in de functie stopt, dus zal T wel een Boolean zijn. Dit wordt ook wel 'type inference' genoemt.
 

  1: ' Het volgende...
  2: DoeIets(Of Boolean)(bool)
  3: ' Kan dus afgekort worden tot...
  4: DoeIets(bool)

Gaaf toch? Dit kan overigens alleen als T een input parameter van de functie is. Anders zal u alsnog (Of Boolean) moeten typen (het type kan dan namelijk niet ge-'inferred' worden).
Laten we nu eens kijken naar een ingewikkelder voorbeeld. De volgende functie krijgt een Dictionary (een lijstje van waarden die gekoppeld zijn aan unieke sleutel-waarden) en geeft van de waarden het Object terug met de middelste waarde.

  1: Public Function ZoekMiddelsteWaarde(Of TKey, TValue As IComparable(Of Tvalue))(Byval dictionary As IDictionary(Of TKey, TValue)) As TValue
  2:    ' Als de dictionary geen waarden heeft geef dan Nothing (default waarde) terug.
  3:    If dictionary.Count > 0 Then
  4:       Dim list As New List(Of TValue)(dictionary.Values)
  5:       ' We kunnen de lijst sorteren omdat iedere waarde een IComparable is.
  6:       list.Sort
  7:       ' Geef de middelste waarde terug.
  8:       Return list(list.Count / 2)
  9:    Else
 10:       Return Nothing
 11:    End If
 12: End Function
 13: 
 14: ' Gebruik.
 15: Dim dict As New Dictionary(Of Integer, String)
 16: dict.Add(1, "D")
 17: dict.Add(2, "I")
 18: dict.Add(3, "Z")
 19: dict.Add(4, "T")
 20: dict.Add(5, "F")

21: ' Sortering is nu D, F, I (midden), T, Z. letter krijgt dus de waarde "I".

 22: Dim letter As String = ZoekMiddelsteWaarde(dict)
 23: dict.Add(6, "H")
 24: ' Bij een even aantal geeft de functie de 'hoogste middelste' terug.
 25: ' In dit geval nog steeds "I".
 26: Dim letter2 As String = ZoekMiddelsteWaarde(dict)

U ziet dat het gebruik van die functie stukken makkelijker is dan de functie zelf. Het zou u bijna gaan duizelen door al die Generics. Het mooie aan deze functie is echter dat deze ook werkt voor een dictionary met Strings en Integers (net andersom dus), of Decimals en Booleans, of Integers en Personen (mits de Persoon klasse de IComparable(Of Persoon)) implementeert natuurlijk). U ziet, de mogelijkheden zijn eindeloos.