Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einführung
2 Grundlagen der Sprachsyntax
3 Klassendesign
4 Weitere Datentypen
5 Multithreading
6 Collections und LINQ
7 Eingabe und Ausgabe
8 Anwendungen: Struktur und Installation
9 Code erstellen und debuggen
10 Einige Basisklassen
11 Windows-Anwendungen erstellen
12 Die wichtigsten Steuerelemente
13 Tastatur- und Mausereignisse
14 MDI-Anwendungen
15 Grafiken mit GDI+
16 Drucken
17 Entwickeln von Steuerelementen
18 Programmiertechniken
19 WPF – Grundlagen
20 Layoutcontainer
21 WPF-Steuerelemente
22 Konzepte von WPF
23 Datenbankverbindung mit ADO.NET
24 Datenbankabfragen mit ADO.NET
25 DataAdapter
26 Offline mit DataSet
27 Datenbanken aktualisieren
28 Stark typisierte DataSets
A Anhang: Einige Übersichten
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual Basic 2008 von Andreas Kuehnel, Stephan Leibbrandt
Das umfassende Handbuch
Buch: Visual Basic 2008

Visual Basic 2008
3., aktualisierte und erweiterte Auflage, geb., mit DVD
1.323 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1171-0
Pfeil 4 Weitere Datentypen
Pfeil 4.1 Module
Pfeil 4.2 Strukturen
Pfeil 4.2.1 Vor- und Nachteile von Referenz- und Werttypen
Pfeil 4.2.2 Initialisierung und Konstruktoren
Pfeil 4.2.3 Boxing
Pfeil 4.2.4 Innere Strukturen
Pfeil 4.2.5 ValueType
Pfeil 4.3 Enumerationen
Pfeil 4.3.1 Datentyp
Pfeil 4.3.2 System.Enum
Pfeil 4.4 Generisches
Pfeil 4.4.1 Einschränkungen
Pfeil 4.4.2 Innere generische Typen
Pfeil 4.4.3 Vererbung und Überladung
Pfeil 4.4.4 Sichtbarkeit
Pfeil 4.4.5 Bindung
Pfeil 4.4.6 Rekursive Typen
Pfeil 4.4.7 Generische Typen in .NET
Pfeil 4.5 Werttypen mit dem Wert Nothing
Pfeil 4.5.1 Typumwandlungen
Pfeil 4.5.2 Logische Operatoren
Pfeil 4.5.3 Boxing
Pfeil 4.6 Anonyme Klassen
Pfeil 4.6.1 Automatische Namen
Pfeil 4.6.2 Schlüssel und Equals
Pfeil 4.6.3 Innere anonyme Klassen
Pfeil 4.6.4 Generierter Code
Pfeil 4.7 Spezielle Typen
Pfeil 4.8 Attribute
Pfeil 4.8.1 Beispiel: Bedingte Kompilierung
Pfeil 4.8.2 Codeerzeugung: DesignerGenerated
Pfeil 4.8.3 Standardinstanzen: MyGroupCollectionAttribute
Pfeil 4.8.4 Klassenerweiterungen: Extension
Pfeil 4.8.5 Attribute abfragen
Pfeil 4.8.6 Benutzerdefinierte Attribute


Rheinwerk Computing - Zum Seitenanfang

4.4 Generisches Zur nächsten ÜberschriftZur vorigen Überschrift

Das Rad neu erfinden … oder besser doch nicht?

Die Tätigkeit eines Programmierers ist, neben den grandiosen Momenten des Erfolgs, durch ein gerüttelt Maß an Routine gekennzeichnet. Im Alltag tauchen immer wieder Problemstellungen auf, die sehr ähnlich sind, aber eben doch nicht identisch. Häufig ist der einzige Unterschied, dass Daten unterschiedlichen Typs vorliegen, die Operationen auf den Daten aber im Wesentlichen gleich bleiben. Ein Beispiel ist die Berechnung eines Maximums aus Werten, so dass der richtige Datentyp zurückgegeben wird und nicht einfach der »maximal mögliche« Double. Da Visual Basic mit der dringend zu empfehlenden Compileroption Strict eine strenge Typisierung aller Variablen erzwingt, sind Programme nicht einfach für andere Datentypen zu übernehmen. Die Vorgehensweise, ein Programmteil zu kopieren und die Datentypen anzupassen, ist keinesfalls eine gute Idee. Zum einen ist es zeitaufwändig, die ganzen Kopien immer auf dem neuesten Stand zu halten, zum anderen passiert es sehr leicht, dass man die Korrektur in einer der Kopien vergisst. Daher bietet Visual Basic die Möglichkeit, Definitionen so zu formulieren, dass die Datentypen »austauschbar« sind und erst bei Verwendung einer Klasse oder Methode festgelegt werden. Da dies im Code erfolgt, hat der Compiler weiterhin die Möglichkeit, eine strenge Typisierung zu erzwingen und Ihnen so bei der Fehlersuche zu helfen.

Syntaktisch werden ein oder mehr Typparameter hinter einem Bezeichner eingeführt. Diese Typparameter können dann im selben Codeblock wie jeder andere Typ verwendet werden. Bei der Nutzung wird dann für jeden Typparameter ein konkreter Datentyp angegeben. Die folgenden Syntaxvarianten enthalten alle Arten von Definitionen, die Typparameter einführen dürfen (die letzten vier sind die einzigen Typmitglieder). Optionale Teile sind in eckige Klammern gesetzt, und die kursiv gesetzten Namen müssen Sie Ihren Bedürfnissen entsprechend anpassen, insbesondere besteht Wahlfreiheit für voneinander unabhängige Typparameter. Wie üblich verbindet der Unterstrich zwei Codezeilen zu einer logischen Zeile.


[<Modifikatoren>] Class Name(Of Typparameter [, ...]) 
  [<Definitionen>] 
End Class

[<Modifikatoren>] Structure Name(Of Typparameter [, ...]) 
  Variablen/Ereignisdeklaration 
  [<Definitionen>] 
End Structure

[<Modifikatoren>] Interface IName(Of Typparameter [, ...]) 
  [<Definitionen>] 
End Interface

[<Sichtbarkeit>] Delegate Sub Name(Of Typparameter [, ...])([<Parameter>])

[<Sichtbarkeit>] Delegate Function _ 
  Name(Of Typparameter [, ...])([<Parameter>]) As Typ

[<Modifikatoren>] Sub name[(Of TypparameterX [, ...])]([<Parameter>]) _ 
[Implements Schnittstelle(Of TypparameterY [, ...]).mitglied [, ...]] 
  [<Anweisungen>] 
End Sub

[<Mod.>] Function name[(Of TypparameterX [, ...])]([<Parameter>]) As Typ _ 
[Implements Schnittstelle(Of TypparameterY [, ...]).mitglied [, ...]] 
  [<Anweisungen>] 
End Function


Hinweis
Definitionen mit Typparametern werden generisch genannt.


Auf einige Besonderheiten möchte ich gezielt hinweisen:

  • Module (Abschnitt 4.1, »Module«), Enumerationen (Abschnitt 4.3, »Enumerationen«) und anonyme Klassen (Abschnitt 4.6, »Anonyme Klassen«) können keine Typparameter einführen, wohl aber solche aus umliegenden Quelltextblöcken benutzen.
  • Felder, Eigenschaften, Ereignisse sowie Operatoren und externe Funktionen können keine Typparameter einführen, wohl aber solche aus umliegenden Quelltextblöcken benutzen.
  • Typmitglieder und Typparameter teilen sich einen Namensraum und müssen zusammengenommen eindeutig sein.
  • Die Anzahl der Typparameter ist ein Teil der Signatur von Datentypen und Methoden. Dies ermöglicht die Überladung von Typen und Methoden nur auf Basis der Anzahl der Typparameter. Definitionen, die sich nur durch die Existenz von Typparametern unterscheiden, haben dennoch nichts miteinander zu tun und dürfen daher nebeneinander existieren (Methodensignaturen sind in Abschnitt 3.3.1, »Prinzip«, beschrieben).
  • Typparameter sind bezüglich der Deklaration ein Quelltextkonstrukt und gelten nur innerhalb desselben Codeblocks im Quelltext bis zur korrespondierenden End-Anweisung bzw. bis zum Zeilenende bei Delegates.
  • Typparameter können nicht vererbt und nicht mit Typnamen qualifiziert werden.
  • Typparameter von mit Partial aufgeteilten Definitionen müssen identisch übereinstimmen, selbst die Bezeichner der Typparameter müssen übereinstimmen.
  • Generische Methoden können keine Ereignisbehandlung mit Handles deklarieren.
  • Ein namensgleicher Typparameter verdeckt den Typparameter des umschließenden Codeblocks.
  • Typparameter können eingeschränkt werden, siehe Abschnitt 4.4.1, »Einschränkungen«.

Hinweis
Generische Typen, in denen zumindest ein Teil der Typparameter durch konkrete Typen festgelegt sind, werden als konstruiert bezeichnet. Nicht vollständig festgelegte Typen heißen offen.


Die Nutzung der Typparameter im Code ist denkbar einfach. Statt eines konkreten Typs geben Sie einfach einen Typparameter an. Das folgende Fragment skizziert das Vorgehen:


Class Generisch(Of Typ) 
  Public Variable As Typ 
  ... 
End Class


Hinweis
Der Typ jeder Variablen liegt zur Laufzeit komplett fest, er ist nie offen.


Schauen wir uns als erstes Beispiel eine generische Methode mit einem Typparameter an. Sie tauscht die Werte zweier Variablen gegeneinander aus. Um die Datentypen flexibel zu halten, wird überall in der Methode der Typparameter Typ verwendet. Zum Vergleich ist auch eine Variante mit »normalen« Datentypen definiert. Es ist also möglich, generische und normale Definitionen parallel zu halten. Ob das wirklich sinnvoll ist und nicht eher Verwirrung stiftet, entscheiden Sie bitte im Einzelfall selbst. In der Methode Test() wird dreimal getauscht, wobei im letzten Aufruf der Compiler den Datentyp automatisch aus den übergebenen Werten ermittelt. In diesem letzten Fall ist es reine Geschmackssache, ob Sie die Formulierung mit Of bevorzugen oder lieber den Compiler die Typen von Typparametern ermitteln lassen. Manchmal jedoch scheitert der Compiler, und Sie müssen die Variante mit Of verwenden. Oder es liegt eine konkurrierende nichtgenerische Definition wie im zweiten Aufruf vor, die bevorzugt wird, da sie »spezifischer« ist. Ohne generische Datentypen müssten Sie für jeden Datentyp eine neue Methode schreiben, hier hingegen kommen Sie durch den Typparameter mit einer einzigen Methode hin.


'...\Datentypen\Generisch\Methode.vb

Option Strict On 
Namespace Datentypen 
  Module Methode

    Sub Tausch(Of Typ)(ByRef links As Typ, ByRef rechts As Typ) 
      Dim temp As Typ = links 
      links = rechts 
      rechts = temp 
    End Sub

    Sub Tausch(ByRef links As Integer, ByRef rechts As Integer) 
      Console.WriteLine("Nichtgenerischer Aufruf:") 
      Dim temp As Integer = links 
      links = rechts 
      rechts = temp 
    End Sub

    Sub Test()

      Dim w1 As Integer = 5, w2 As Integer = 17 
      Console.WriteLine("Vorher  : {0,3} {1,3}", w1, w2) 
      Tausch(Of Integer)(w1, w2) 
      Console.WriteLine("Nachher : {0,3} {1,3}", w1, w2) 
      Tausch(w1, w2) 
      Console.WriteLine("Original: {0,3} {1,3}", w1, w2)

      Dim d1 As Date = Now, d2 As Date = Now.AddDays(1.234) 
      Console.WriteLine("Vorher  : {0,22} {1,22}", d1, d2) 
      Tausch(d1, d2) 
      Console.WriteLine("Nachher : {0,22} {1,22}", d1, d2)

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Werte werden in allen Fällen korrekt getauscht:

Vorher  :   5  17 
Nachher :  17   5 
Nichtgenerischer Aufruf: 
Original:   5  17 
Vorher  :  3/10/2008 12:41:09 PM   3/11/2008 6:18:06 PM 
Nachher :   3/11/2008 6:18:06 PM  3/10/2008 12:41:09 PM

Häufiger als Methoden kommen generische Datentypen zum Einsatz. Als Beispiel folgt ein sogenannter Stapel, der Werte aufnimmt und in umgekehrter Reihenfolge wieder abgibt (englisch LIFO: last in first out). Bei Warteschlangen behält man eher die Reihenfolge bei (englisch FIFO: first in first out). Der Typ der gespeicherten Werte wird durch einen Typparameter auf Klassenebene spezifiziert. Die Methoden zur Änderung des Stapels verwenden denselben Typparameter (ein Typparameter auf Methodenebene kann zu inkonsistenten Datentypen führen).


'...\Datentypen\Generisch\Typ.vb

Option Strict On 
Namespace Datentypen

  Class Stapel(Of Typ)

    Private werte() As Typ = New Typ() {}

    Private ende As Integer = –1

    Sub Hinzufügen(ByVal wert As Typ) 
      If ende >= werte.Length – 1 Then ReDim Preserve werte(werte.Length) 
      ende += 1 
      werte(ende) = wert 
    End Sub

    Function Wegnehmen() As Typ 
      If ende < 0 Then Throw New InvalidOperationException("Stapel leer!") 
      ende -= 1 
      Return werte(ende + 1) 
    End Function

  End Class 
  ... 
End Namespace

In Test() verwenden wir einen Stapel aus Zeichen. Die Buchstaben einer Zeichenkette werden dem Stapel hinzugefügt und dann wieder entnommen. Die Beispielzeichenkette hört sich rückwärts gelesen genauso an wie vorwärts gelesen. So etwas nennt man Palindrom.


'...\Datentypen\Generisch\Typ.vb

Option Strict On 
Namespace Datentypen 
  ... 
  Module Typ 
    Sub Test()

      Dim st As Stapel(Of Char) = New Stapel(Of Char) 
      Dim palindrom As String = "nie leg Raps neben Spargel ein"

      For Each c As Char In palindrom : st.Hinzufügen(c) : Next

      Try 
        For no As Integer = 0 To 50 : Console.Write(st.Wegnehmen()) : Next 
      Catch ex As Exception 
        Console.WriteLine(Environment.NewLine & ex.Message) 
      End Try 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt die umgekehrte Entnahme der Werte und die Ausnahme, die durch den Entnahmeversuch aus einem leeren Stapel ausgelöst wird.

nie legrapS neben spaR gel ein 
Stapel leer!

Auch Funktionszeiger dürfen Typparameter enthalten. Mit einem generischen Delegate lassen sich viele Typen von Ereignissen gleichzeitig beschreiben. Das folgende Beispiel definiert ein Delegate, das von zwei Typparametern abhängt. Dadurch lassen sich in den beiden folgenden Klassen die Ereignisse durch dasselbe generische Delegate mit unterschiedlichen konkreten Datentypen typisieren. Das Modul Delegates enthält eine Methode zur Behandlung der Ereignisse, die in Test() ausgelöst werden.


'...\Datentypen\Generisch\Delegate.vb

Option Strict On 
Namespace Datentypen

  Delegate Sub Handler(Of S, A)(ByVal sender As S, ByVal argumente As A)

  Class Button 
    Event Click As Handler(Of Button, Date) 
    Sub Drücken() 
      RaiseEvent Click(Me, Now) 
    End Sub 
  End Class

  Class Liste 
    Event Click As Handler(Of Liste, Integer) 
    Sub Drücken(ByVal eintrag As Integer) 
      RaiseEvent Click(Me, eintrag) 
    End Sub 
  End Class

  Module Delegates 
    Sub Element(ByVal sender As Object, ByVal daten As Object) 
      Console.WriteLine("{0} gedrückt: {1}", sender.GetType().Name, daten) 
    End Sub

    Sub Test() 
      Dim b As New Button(), l As New Liste() 
      AddHandler b.Click, AddressOf Element 
      AddHandler l.Click, AddressOf Element 
      b.Drücken() : l.Drücken(27) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Behandlungsmethode wird beide Male korrekt aufgerufen:

Button gedrückt: 3/10/2008 3:23:05 PM 
Liste gedrückt: 27

Hinweis
Typparameter, die bei der Nutzung eines generischen Typs fehlen, versucht der Compiler automatisch zu ermitteln.



Rheinwerk Computing - Zum Seitenanfang

4.4.1 Einschränkungen Zur nächsten ÜberschriftZur vorigen Überschrift

Es ist nicht möglich, die vollständige Allgemeinheit eines Datentyps immer aufrechtzuerhalten. Das Einzige, was alle Objekte gemeinsam haben, sind die Methoden der Klasse Object. Damit allein lässt sich nicht viel machen. Daher ist es sehr oft sinnvoll, den Typparameter ein wenig festzulegen, zum Beispiel durch die Angabe einer Schnittstelle, die er implementiert. Das hat zwei Effekte:

  • Es ist leichter, konkreten Code für eingeschränkte Typparameter zu formulieren, da Zugriff auf die Funktionalität all der Typen besteht, die die Einschränkung erfüllen.
  • Nur Datentypen, die die Einschränkungen erfüllen, können bei der Nutzung des generischen Typs oder der generischen Methode verwendet werden.

Die Spezifikation der Einschränkung lehnt sich an die Deklaration von Variablen an. In den folgenden beiden Syntaxvarianten sind Alternativen durch einen senkrechten Strich getrennt und optionale Teile in eckige Klammern gesetzt. Kursiv gesetzte Teile müssen Sie Ihren Bedürfnissen anpassen. Wie üblich verbindet der Unterstrich zwei Codezeilen zu einer logischen Zeile.


Typparameter As Klasse | Schnittstelle | anderer Typparameter | _ 
                New | Class | Structure 
Typparameter As {Einschränkung [, ...]}

Auch hier möchte ich auf einige Besonderheiten hinweisen:

  • Module, Strukturen und Enumerationen können nicht in der As-Klausel verwendet werden.
  • Typeinschränkungen ergänzen sich, jede zusätzliche erweitert die Zugriffsmöglichkeiten. Die anderen (New, Class, Structure) engen die erlaubten konkreten Typen ein.
  • Datentypen in der As-Klausel dürfen generisch sein und den Typparameter selbst enthalten.
  • Einschränkungen müssen mindestens so sichtbar sein wie die Definition, die sie enthält.
  • Es darf nur eine Klasse gleichzeitig als Einschränkung deklariert werden, mehrere Schnittstellen gleichzeitig sind erlaubt.
  • Die Kombination eines konkreten Typs mit Class oder Structure ist nicht erlaubt.
  • Typparameter mit Structure-Einschränkung sind in der As-Klausel verboten.
  • Signaturen werden als gleich angesehen, wenn sie sich nur durch die Einschränkungen der Typparameter unterscheiden.
  • Datentypen in der As-Klausel dürfen nicht mit NotInheritable gekennzeichnet sein, nullbare Typen (siehe Abschnitt 4.5, »Werttypen mit dem Wert Nothing«) sind durch Structure ausgeschlossen.
  • Array, Delegate, MulticastDelegate, Enum, Object und ValueType im Namensraum System sind nicht als Datentypen in der As-Klausel zugelassen.
  • Bei Vererbung sind durch Überschreiben eines Elternklassenmitglieds oder Implementieren eines Schnittstellenmitglieds die beiden letzten Punkte verletzbar (bezüglich des Typparameters sind dann nur Typumwandlungen möglich, die DirectCast erlaubt).

Den Einsatz einer Einschränkung zeigt das nächste Beispiel, das das Maximum einer beliebigen Anzahl von Werten ermittelt, indem es die Methode CompareTo() der Schnittstelle IComparable verwendet. In der As-Klausel wird der Typparameter selbst verwendet, um die Methodenparameter typrichtig miteinander zu vergleichen. Bitte beachten Sie, dass in Test() der Compiler den Datentyp des Typparameters T von Max() automatisch ermitteln kann (sonst müssten Sie Max(Of Single)(17, 9, 102.7F, 89, –281, 2) schreiben).


'...\Datentypen\Generisch\Einschränkung.vb

Option Strict On 
Namespace Datentypen 
  Module Einschränkung

    Function Max(Of T As IComparable(Of T))(ByVal ParamArray w() As T) As T 
      Dim m As T 
      For Each ww As T In w : m = If(ww.CompareTo(m) > 0, ww, m) : Next 
      Return m 
    End Function

    Sub Test() 
      Dim maximum As Single = Max(17, 9, 102.7F, 89, –281, 2) 
      Console.WriteLine("Maximum: {0}", maximum) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Das Maximum wird korrekt bestimmt:

Maximum: 102.7

Ein generischer Typ kann auch mehrere Einschränkungen haben, die in geschweiften Klammern stehen. Im Code kann auf die Mitglieder aller Typen zugegriffen werden, die in der Einschränkung spezifiziert worden sind. Nicht alle Kombinationen sind dabei erlaubt (siehe oben). Im folgenden Beispiel werden die Klasse Kämpfer und die Schnittstelle IPrüfling als Typparameter in der Methode Info() zugelassen. Daher kann auf Mitglieder beider Typen zugegriffen werden: auf Prüfling aus Kämpfer und auf Name aus IPrüfling.


'...\Datentypen\Generisch\MehrfachEinschraenkung.vb

Option Strict On 
Namespace Datentypen

  Interface IPrüfling 
    Function Name() As String 
  End Interface

  Class Kämpfer : Implements IPrüfling 
    Private wer As String 
    Sub New(ByVal name As String) 
      wer = name 
    End Sub

    Function Prüfling() As String Implements IPrüfling.Name 
      Return wer 
    End Function 
  End Class

  Module MehrfachEinschraenkungen

    Sub Info(Of T As {Kämpfer, IPrüfling})(ByVal wer As T) 
      Console.WriteLine("Prüfling: {0}", wer.Prüfling()) 
      Console.WriteLine("Prüfling: {0}", wer.Name()) 
    End Sub

    Sub Test() 
      Dim kämpfer As New Kämpfer("Liu Yu Te") 
      Info(kämpfer) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Beide Zugriffe in Info() erzeugen die gleiche Ausgabe:

Prüfling: Liu Yu Te 
Prüfling: Liu Yu Te

Structure

Anders als es der Name vermuten lässt, beschränkt man sich mit der Einschränkung Structure nicht auf Strukturen, sondern auf beliebige von ValueType abgeleitete Typen. Es werden zum Beispiel auch Primitive und Enumerationen erfasst. Nicht erlaubt sind jedoch nullbare Typen (siehe Abschnitt 4.5, »Werttypen mit dem Wert Nothing«). Im folgenden Codefragment wird in der Methode Test() die Methode Ausgabe() mit einer Struktur, einer Fließkommazahl sowie einer Enumeration als Parameter aufgerufen. Alle sind Werttypen und erfüllen die Structure-Einschränkung der Methode. Die auskommentierte Zeile dagegen würde zu einem Compilerfehler führen.


'...\Datentypen\Generisch\Structure.vb

Option Strict On 
Namespace Datentypen

  Structure Zahl 
    Friend Wert As Integer 
    Public Overrides Function ToString() As String 
      Return Wert.ToString() 
    End Function 
  End Structure

  Enum Schalter : Aus : An : End Enum

  Module StructureEinschränkung

    Sub Ausgabe(Of Typ As Structure)(ByVal wert As Typ) 
      Console.WriteLine("Wert {0}", wert) 
    End Sub

    Sub Test() 
      Dim z As Zahl : z.Wert = 99 : Ausgabe(z) 
      Dim w As Double = 77.2 : Ausgabe(w) 
      Dim s As Schalter = Schalter.Aus : Ausgabe(s) 
      'Ausgabe(New Object())                               'Compilerfehler!! 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt die korrekte Arbeitsweise:

Wert 99 
Wert 77.2 
Wert Aus

Hinweis
Ohne Structure-Einschränkung können Typen mit Is und IsNot verglichen werden, auch wenn die konkreten Typen Werttypen sind (über den Sinn entscheiden Sie bitte selbst).


Class

Die Einschränkung Class reduziert die möglichen Datentypen für den Typparameter auf Referenztypen und verbietet alle Werttypen. Es stellt damit fast das Pendant zur Structure-Einschränkung dar. Das »fast« bezieht sich darauf, dass auch hier die nullbaren Typen (siehe Abschnitt 4.5, »Werttypen mit dem Wert Nothing«) ausgeschlossen sind. So eine Art Ausschluss ist sinnvoll, wenn zum Beispiel sichergestellt werden soll, dass der Kopiermechanismus der Werttypen nicht stattfindet oder Operatoren wie Is verwendet werden, die nur für Referenztypen erlaubt sind. Das folgende Codefragment beschränkt den Typparameter der Methode Ausgabe() mit Class und ruft sie in Test() mit einem Objekt und einer Schnittstellenreferenz auf. Die auskommentierten Zeilen würden zu einem Compilerfehler führen, da Ausgabe() mit einem Werttyp aufgerufen wird.


'...\Datentypen\Generisch\Class.vb

Option Strict On 
Namespace Datentypen

  Structure Wert : Friend Zahl As Integer : End Structure 
  Enum Antwort : Ja : Nein : End Enum

  Interface IMarkierung : End Interface 
  Class Implementierung : Implements IMarkierung : End Class

  Module ClassEinschränkung

    Sub Ausgabe(Of Typ As Class)(ByVal wert As Typ) 
      Console.WriteLine("Wert initialisiert: {0}", wert IsNot Nothing) 
    End Sub

    Sub Test()

      Ausgabe(New Object()) 
      Dim mark As IMarkierung = New Implementierung() : Ausgabe(mark) 
      mark = Nothing : Ausgabe(mark)

      'Ausgabe(New Wert())                                 'Compilerfehler!! 
      'Ausgabe(77.2)                                       'Compilerfehler!! 
      'Ausgabe(Antwort.Nein)                               'Compilerfehler!! 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Der Is-Operator arbeitet erwartungsgemäß:

Wert initialisiert: True 
Wert initialisiert: True 
Wert initialisiert: False

New

Soll ein Typparameter zur Erzeugung von Objekten mit dem New-Operator herangezogen werden, muss er New als Einschränkung spezifizieren. Damit sind Typen erlaubt, die nicht mit MustInherit gekennzeichnet sind und einen sichtbaren parameterlosen Konstruktor zur Verfügung stellen. Dieser ist auch der einzige, mit dem ein Typparameter ein Objekt erzeugen kann. Die folgenden Codefragmente erzeugen daher auch einen Compilerfehler: Dem ersten fehlt die New-Einschränkung, und das zweite verwendet einen Konstruktorparameter.

Function Neu(Of Typ)() As Typ 
  Return New Typ()                                    'Compilerfehler!! 
End Function

Function Neu(Of Typ As New)() As Typ 
  Return New Typ(1)                                   'Compilerfehler!! 
End Function

Das folgende Beispiel protokolliert in der Methode Neu() die Objekterzeugung. Durch die Einschränkung New für den Typparameter können Objekte über den Typparameter erzeugt werden. In Test() werden verschiedene Objekte erzeugt. Die auskommentierten Zeilen würden zu einem Compilerfehler führen, da die verwendeten Datentypen nicht die New-Einschränkung erfüllen – der erste, weil er durch MustInherit abstrakt ist, und der zweite, weil er keinen parameterlosen Konstruktor mit ausreichender Sichtbarkeit zur Verfügung stellt.


'...\Datentypen\Generisch\New.vb

Option Strict On 
Namespace Datentypen

  MustInherit Class Abstrakt : End Class

  Class Privat 
    Private Sub New() 
    End Sub 
  End Class

  Module NewEinschränkung

    Function Neu(Of Typ As New)() As Typ 
      Console.WriteLine("Erzeuge Objekt vom Typ {0}.", GetType(Typ).Name) 
      Return New Typ() 
    End Function

    Sub Test()

      Neu(Of Object)() 
      Dim rnd As Random = Neu(Of Random)()

      'Dim abs As Abstrakt = Neu(abs)                      'Compilerfehler!! 
      'Dim prv As Privat = Neu(prv)                        'Compilerfehler!!

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Alle mit der Methode erzeugten Objekte werden protokolliert.

Erzeuge Objekt vom Typ Object. 
Erzeuge Objekt vom Typ Random.

Stolperfallen

Leider entspricht der Compiler nicht in allen Punkten der Sprachspezifikation von Visual Basic. Das folgende Codefragment sollte eigentlich gar nicht übersetzt werden, da die New-Einschränkung vom Rennauto nicht erfüllt wird, weil kein parameterloser Konstruktor existiert. Besonders ärgerlich ist, dass der erste Fahren()-Aufruf gänzlich ignoriert wird und der zweite komplett durchläuft, so dass kein Anhaltspunkt für eine Fehlersuche existiert.


'...\Datentypen\Generisch\Stolperfallen.vb

Option Strict On 
Namespace Datentypen

  Class Auto 
    Sub Fahren() 
      Console.WriteLine("{0} fährt.", Me.GetType().Name) 
    End Sub

  End Class

  Class Rennauto : Inherits Auto 
    Sub New(ByVal fahrer As String) 
    End Sub 
  End Class

  Module Stolperfallen

    Sub Fahren(Of T As {New, Auto})(ByVal ParamArray w() As T) 
      For Each a As Auto In w : a.Fahren() : Next 
    End Sub

    Sub Test() 
      Fahren(New Rennauto("Laie")) 
      Fahren(New Rennauto("Laie"), New Auto()) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass der erste Fahren()-Aufruf fehlt:

Rennauto fährt. 
Auto fährt.

Hinweis
Ohne ParamArray tritt das Problem nicht auf.



Rheinwerk Computing - Zum Seitenanfang

4.4.2 Innere generische Typen Zur nächsten ÜberschriftZur vorigen Überschrift

Ist ein Datentyp in einem anderen generisch, verhält er sich genauso wie ein nichtgenerischer. Er hat, wie jedes Mitglied des äußeren Typs, Zugriff auf dessen Typparameter. Bei der Verwendung sind alle Typparameter, ob des äußeren oder des inneren Typs, mit konkreten Typen oder Typparametern des umschließenden Typs zu belegen. Das nächste Beispiel definiert die generische Klasse Motor als inneren Typ der ebenfalls generischen Klasse Airbus. Im Konstruktor der inneren Klasse werden die Typparameter der äußeren und der inneren Klasse verwendet. Die Verwendung des Typs zeigt, dass jeder generische Typ seine eigenen Typen mittels Of festlegt.


'...\Datentypen\Generisch\InnereTypen.vb

Option Strict On 
Namespace Datentypen

  Class Jet : End Class 
  Class Rakete : End Class

  Class Airbus(Of Typ) 
    Class Motor(Of Antrieb) 
      Sub New(ByVal flugzeug As Typ, ByVal motor As Antrieb) 
        Console.WriteLine("Flugzeug {0} mit Motor {1}", flugzeug, motor) 
      End Sub 
    End Class 
  End Class

  Module InnereTypen 
    Sub Test() 
      Dim m As New Airbus(Of Jet).Motor(Of Rakete)(New Jet(), New Rakete()) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Für die Verwendung der Typparameter spielt es keine Rolle, woher sie kommen. Die Ausgabe bereitet daher keine Probleme (auch wenn in der Realität Airbus so etwas noch nicht in der zivilen Luftfahrt verwendet).

Flugzeug Generisch.Datentypen.Jet mit Motor Generisch.Datentypen.Rakete

Rheinwerk Computing - Zum Seitenanfang

4.4.3 Vererbung und Überladung Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn eine generische Klasse beerbt wird, muss für jeden Typparameter in der Implements-Klausel ein Typ angegeben werden. Da dies wiederum ein Typparameter sein darf, aber nicht sein muss, kann eine Kindklasse wieder generisch sein oder aber konkret (geschlossen). Diese Flexibilität führt dazu, dass ein und dieselbe Methode in einer Klasse oder Schnittstelle mehrfach überschrieben bzw. implementiert werden kann. Bei der Kindklasse ist es wichtig, dass sie genauso viele generische Typen (englisch arity) verwendet wie die Elternklasse und Einschränkungen für Typparameter in der Elternklasse in der Kindklasse nicht verletzt werden. Dies gilt für alle Arten generischer Typen und Methoden.


Hinweis
Generische Methoden, die mit Overrides oder Implements durch verschiedene aktuelle Typargumente mehrfach definiert werden, dürfen sich nicht überlappen.


Typen

Eine Kindklasse spezialisiert immer die Elternklasse. Dies bezieht sich insofern auch auf die Typparameter, als dass sie mindestens so speziell sein müssen wie in der Elternklasse. Im folgenden Beispiel wird die generische Klasse Box etwas spezieller in der Klasse ElternBox durch die Beschränkung des Typparameters auf Kind. Typparametern können in einer Kindklasse also zusätzliche Beschränkungen auferlegt werden. Einmal gemachte Einschränkungen in der Elternklasse dagegen können in der Kindklasse nicht zurückgenommen werden. Da die Klasse ElternBox einen Typparameter enthält, ist sie auch weiterhin generisch. In der dritten Stufe wird daraus eine einfache Klasse durch die Festlegung des Typparameters auf Kind. Die auskommentierte Zeile würde einen Compilerfehler erzeugen, da Date nicht die Einschränkung des Typparameters in ElternBox erfüllt. In der Methode Test() ist zu sehen, dass Objekte von jeder der Stufen generiert werden können.


'...\Datentypen\Generisch\Kindtyp.vb

Option Strict On 
Namespace Datentypen

  Class Eltern : End Class 
  Class Kind : Inherits Eltern : End Class

  Class Box(Of Was) : End Class 
  Class ElternBox(Of W As Eltern) : Inherits Box(Of W) : End Class 
  Class KindBox : Inherits ElternBox(Of Kind) : End Class 
  'Class Termin : Inherits ElternBox(Of Date) : End Class  'Compilerfehler!!

  Module Kindtyp 
    Sub Test() 
      Dim direkt As New Box(Of Kind) 
      Dim eltern As New ElternBox(Of Kind) 
      Dim kind As New KindBox()

      Console.WriteLine("Typen: {0} {1} {2}", direkt.GetType().Name, _ 
        eltern.GetType().Name, kind.GetType().Name) 
      Console.WriteLine("Vererbt: {0}", TypeOf kind Is Box(Of Kind)) 
      Console.WriteLine("Umwandelbar: {0}", _ 
                        eltern.GetType() Is GetType(ElternBox(Of Eltern)()))

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Ausgabe zeigt etwas ungewohnte Namen von Klassen. Der Akzent kennzeichnet generische Typen, und die Zahl gibt die Anzahl Typparameter an:

Typen: Box`1 ElternBox`1 KindBox 
Vererbt: True 
Umwandelbar: False

Die beiden letzten Ausgaben machen deutlich:


Es gibt keine Vererbung bezüglich der konkreten Typen von Typparametern.


Anders formuliert:


Eine Vererbungshierarchie generischer Typen hat identische konkrete Typen für alle Typparameter.


Damit ist das folgende Codefragment fehlerhaft:

Dim b As ElternBox(Of Eltern) = New ElternBox(Of Kind)     'Compilerfehler!!

Hinweis
Die Ableitung einer generischen Klasse mit einem Typparameter statt mit einem konkreten Typ erzeugt wieder eine generische Klasse.


Überschreiben und Überladen

Definiert eine Elternklasse virtuelle Methoden mit Overridable oder MustOverride, werden diese in Kindklassen mit derselben Signatur und dem Schlüsselwort Overrides überschrieben. Dabei müssen alle Einschränkungen der Typparameter erhalten werden: Eine Kindklasse kann keine neue Einschränkung einführen oder eine vorhandene abändern. Da nur die Anzahl der Typparameter Teil der Signatur ist, ist durch eine Einschränkungsänderung auch kein Überladen möglich. Das folgende Codefragment zeigt, dass eine Umbenennung der Typparameter von Argumentausgabe in der ersten Methode von Konsolenausgabe erlaubt ist. Durch eine geänderte Anzahl der Typparameter stellt die zweite Methode eine Überladung dar. Die auskommentierte Zeile würde einen Compilerfehler auslösen, da eine Einschränkung eines Typparameters die Signatur der darüber stehenden Methode nicht ändert und aus Sicht des Compilers die Methode doppelt vorhanden ist.


'...\Datentypen\Generisch\Kindmethode.vb

Option Strict On 
Namespace Datentypen

  MustInherit Class Argumentausgabe 
    MustOverride Sub Druck(Of T, P)(ByVal arg As T) 
  End Class

  Class Konsolenausgabe : Inherits Argumentausgabe

    Overrides Sub Druck(Of W, F)(ByVal zahl As W) 
      Console.WriteLine("Start: " & zahl.ToString(0)) 
    End Sub

    Overloads Sub Druck(Of W)(ByVal zahl As W) 
      Console.WriteLine("Argument: {0}", zahl) 
    End Sub

    'Overloads Sub Druck(Of W As Class)(ByVal z As W)      'Compilerfehler!!

  End Class

  Module Kindmethode 
    Sub Test() 
      Dim aus As New Konsolenausgabe() 
      aus.Druck(Of Double, Argumentausgabe)(9.8) 
      aus.Druck(9.8) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die erste Ausgabe wird von der überschriebenen Methode erzeugt, die zweite Ausgabe von der überladenen Methode:

Start: 9 
Argument: 9.8

Hinweis
Die Überladung auf Basis der Anzahl der Typparameter gilt genauso für Datentypen.


Ist ein Typparameter durch einen umschließenden Block festgelegt, wird er behandelt wie jeder andere Datentyp und kann auch zur Überladung genutzt werden. Das folgende Codefragment definiert eine Methode, die den Typparameter der Klasse verwendet, und eine Methode mit einem konkreten Datentyp. Auch wenn der Typparameter Art »zufällig« auch String sein kann, liegen zwei verschiedene Definitionen vor. In Test() werden die Methoden mit Objekten verschiedenen Typs aufgerufen.


'...\Datentypen\Generisch\Ueberladung.vb

Option Strict On 
Namespace Datentypen

  Class Daten(Of Art)

    Overloads Shared Sub Druck(ByVal daten As Daten(Of Art)) 
      Console.WriteLine("Allgemein") 
    End Sub

    Overloads Shared Sub Druck(ByVal daten As Daten(Of String)) 
      Console.WriteLine("Speziell") 
    End Sub

  End Class

  Module Überladung 
    Sub Test() 
      Daten(Of String).Druck(New Daten(Of String)()) 
      Daten(Of Integer).Druck(New Daten(Of Integer)()) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die genauer passende Methode wird jeweils gewählt. Es liegt eine Überladung für den konkreten Typ String vor, die, wie die erste Ausgabe zeigt, für String-Daten verwendet wird. Für alle anderen Typen wird die Methode mit dem Typparameter verwendet.

Speziell 
Allgemein

Verdecken

Da Typparameter ein Quelltextkonstrukt sind, kann ein Typparameter durch einen anderen nur im gleichen Quelltextblock abgeschattet werden, nicht durch Vererbung. Das folgende Codefragment zeigt die zweifache Verwendung des Typparameters Art: auf Klassenebene und für die in der Klasse enthaltene Methode. In Test() erfolgt ein Zugriff auf die Methode.


'...\Datentypen\Generisch\Verdecken.vb

Option Strict On 
Namespace Datentypen

  Class Kontakt(Of Art As Structure) 
    Shared Sub Start(Of Art)(ByVal wie As Art) 
      Console.WriteLine("Kontaktart: {0}", wie.GetType().Name) 
    End Sub 
  End Class

  Module Verdecken 
    Sub Test() 
      Kontakt(Of Date).Start("Anruf") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Der Typparameter der Klasse wird ignoriert, da er vom gleichnamigen Typparameter der Methode verdeckt wird. Wäre dem nicht so, würde die Structure-Einschränkung der Klasse nicht vom Typparameter der Methode erfüllt und der Compiler würde einen Fehler melden.

Kontaktart: String

Protected-Mitglieder

Wie bereits in obigem Unterabschnitt Typen erläutert wurde, sind Vererbungshierarchien generischer Klassen mit unterschiedlichen Typparametern komplett getrennt. Folgerichtig sind Zugriffe auf Mitglieder, die mit Protected auf die Vererbungshierarchie beschränkt sind, nur möglich, wenn identisch dieselben Typparameter in Eltern- und Kindklasse vorliegen. Dabei ist ein Typparameter grundsätzlich etwas anderes als ein konkreter Typ, auch wenn beide in einem Spezialfall gleich sein können.

Im folgenden Beispiel wird auf das mit Protected geschützte Feld der Elternklasse in der Kindklasse mit je einer Methode mit konkretem Typ Date und mit Typparameter Art zugegriffen. In der ersten Methode liegt der im vorigen Absatz beschriebene Fall vor, und sie ist auskommentiert, um eine Fehlermeldung des Compilers zu vermeiden. Die Methoden sind unterschiedlich benannt, um die Methode mit dem Typparameter auch mit einem Objekt vom Typ Date aufrufen zu können und nicht immer in der Methode mit Date zu landen. In Test() werden beide Methoden mit den gleichen Datentypen aufgerufen.


'...\Datentypen\Generisch\Verdecken.vb

Option Strict On 
Namespace Datentypen

  Class Verarbeitung(Of Art) 
    Protected daten As Art 
  End Class

  Class Ausgabe(Of Art) : Inherits Verarbeitung(Of Art) 
    Shared Sub Druck(ByVal eingabe As Ausgabe(Of Date)) 
      Console.WriteLine("???") 
      'Console.WriteLine(eingabe.daten)                    'Compilerfehler!! 
    End Sub

    Shared Sub Ausdruck(ByVal eingabe As Ausgabe(Of Art)) 
      Console.WriteLine(eingabe.daten) 
    End Sub 
  End Class

  Module Vererbungshierarchie 
    Sub Test() 
      Ausgabe(Of Date).Druck(New Ausgabe(Of Date)()) 
      Ausgabe(Of Date).Ausdruck(New Ausgabe(Of Date)()) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Wie die Ausgabe zeigt, werden beide Methoden korrekt aufgerufen:

??? 
1/1/0001 12:00:00 AM

Im Rahmen von Typparametern ergibt sich noch eine kleine Besonderheit. Ein generischer Datentyp kombiniert die Sichtbarkeiten der Elemente, aus denen er sich zusammensetzt. Daher kann die Klasse Schnittmenge des folgenden Codefragments nur in Klassen verwendet werden, die sowohl ProtectedFriend ableiten als auch in derselben Anwendung liegen. Diese doppelte Einschränkung ist mit anderen Sprachmitteln von Visual Basic nicht definierbar, da Protected Friend bedeutet, dass der Zugriff entweder in einer Kindklasse oder von innerhalb der Anwendung erfolgen kann.

Class ProtectedFriend 
  Friend Class Anwendung : End Class 
  Protected Class Schnittmenge(Of T As Anwendung) : End Class 
End Class

Hinweis
C# erlaubt Zugriffe auf Protected-Mitglieder unabhängig vom Typparameter.


Schnittstellen (Interface)

Eine generische Schnittstelle stellt im Grunde eine ganze Anzahl von Schnittstellen zur Implementierung zur Verfügung. Jeder bei der Implementation angegebene konkrete Typ erfüllt einen Vertrag mit der Schnittstelle. Das geht so weit, dass selbst in einer Klasse dieselbe generische Schnittstelle durch verschiedene Signaturen implementiert werden kann.

Das folgende Beispiel definiert die generische Schnittstelle IFläche, die in der Klasse Mauer mit zwei verschiedenen Typen implementiert wird. In Test() werden zwei Schnittstellenreferenzen auf dasselbe Objekt definiert und in den Schreibbefehlen zur Ausgabe genutzt.


'...\Datentypen\Generisch\Schnittstellen.vb

Option Strict On 
Namespace Datentypen

  Enum Farbe : Rot = 1 : Blau = 2 : Grün = 4 : End Enum 
  Enum Form : Viereckig : Rund : End Enum

  Interface IFläche(Of Typ) 
    Function Art() As Typ 
  End Interface

  Class Mauer : Implements IFläche(Of Farbe), IFläche(Of Form) 
    Function Ansicht() As Farbe Implements IFläche(Of Farbe).Art 
      Return Farbe.Rot 
    End Function

    Function Oberfläche() As Form Implements IFläche(Of Form).Art 
      Return Form.Viereckig 
    End Function 
  End Class

  Module Schnittstellen 
    Sub Test()

      Dim mauer As New Mauer() 
      Dim farbe As IFläche(Of Farbe) = mauer 
      Dim form As IFläche(Of Form) = mauer 
      'Dim fläche As IFläche(Of Object) = mauer            'Compilerfehler!!

      Console.WriteLine("Farbe {0}={1}", mauer.Ansicht(), farbe.Art()) 
      Console.WriteLine("Form {0}={1}", mauer.Gestalt(), form.Art())

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Zugriffe über die Objekt- und Schnittstellenreferenzen sind gleichwertig:

Farbe Rot=Rot 
Form Viereckig=Viereckig

Die Möglichkeiten der Vielfachimplementierung führen zu ein paar Stolperfallen, von denen das folgende Codefragment zwei enthält. Der erste Fehler entsteht dadurch, dass die Implementierung nicht mehr eindeutig ist, wenn der Datentyp Werkstatt(Of Meister) verwendet wird, da er mit dem konkreten Typ IHandwerker(Of Stift, Meister) konkurriert und die beiden nicht mehr zu trennen sind. Um diese Möglichkeit eines Problems zu vermeiden, verbietet der Compiler diese Art der Definition. Ähnlich gelagert ist der zweite Fall, in dem durch zwei gleiche konkrete Typen die Methode Auftrag der Schnittstelle doppelt vorhanden ist, da sowohl Aufsicht als auch Arbeiter vom Typ Geselle sind.


'...\Datentypen\Generisch\Überlappung.vb

Option Strict On 
Namespace Datentypen

  Class Stift : End Class 
  Class Geselle : Inherits Stift : End Class 
  Class Meister : Inherits Geselle : End Class

  Interface IHandwerker(Of Arbeiter As Stift, Aufsicht As Geselle) 
    Sub Auftrag(ByVal auf As Aufsicht) 
    Sub Auftrag(ByVal arb As Arbeiter) 
    Sub Reparatur(ByVal arb As Arbeiter, ByVal auf As Aufsicht) 
  End Interface

  Class Werkstatt(Of Aufsicht As Geselle) 
    Implements IHandwerker(Of Stift, Meister)              'Compilerfehler!! 
    Implements IHandwerker(Of Stift, Aufsicht) 
    ... 
  End Class

  Class Werkstatt 
    Inherits Werkstatt(Of Geselle) 
    Implements IHandwerker(Of Geselle, Geselle)            'Compilerfehler!! 
    ... 
  End Class 
  ... 
End Namespace

Analog zur Klassenvererbung muss auch die Schnittstellenvererbung die Einschränkungen in der Elternschnittstelle für Typparameter in der Kindklasse erfüllen. Anders als bei Klassen führt ein Abschatten mit Shadows nicht dazu, dass verdeckte Schnittstellenmitglieder der Elternklasse abgeschnitten sind, sondern sie müssen implementiert werden, wenn eine Klasse mit Implements die Schnittstellenimplementierung deklariert. Das folgende Beispiel zeigt diese beiden letzten Punkte, den ersten in den As-Klauseln von IAufräumen sowie ISäubern und den zweiten in der doppelten Implements-Deklaration der Methode Tun() in der Klasse Butler. Die Verwendung der Datentypen in der Methode Test() macht noch auf ein Problem aufmerksam. Analog zu den im Unterabschnitt Typen weiter oben beschriebenen Klassen legt jeder konkrete Typ eine neue Vererbungshierarchie fest, so dass die Zeile im Try-Block zu einem Laufzeitfehler führt, obwohl Profi sich von Laie ableitet.


'...\Datentypen\Generisch\Vererbung.vb

Option Strict On 
Namespace Datentypen

  Class Laie : End Class 
  Class Profi : Inherits Laie : End Class

  Interface IAufräumen(Of T As Laie) 
    Sub Tun() 
  End Interface 
  Interface ISäubern(Of T As Profi) : Inherits IAufräumen(Of T) 
    Shadows Sub Tun() 
  End Interface

  Class Butler : Implements ISäubern(Of Profi) 
    Sub Tun() Implements ISäubern(Of Profi).Tun, IAufräumen(Of Profi).Tun 
      Console.Write("sauber+rein ") 
    End Sub 
  End Class

  Module Vererbung 
    Sub Test()

      Dim b As New Butler() 
      Dim s As ISäubern(Of Profi) = b 
      Try 
        Dim al As IAufräumen(Of Laie) = b 
      Catch ex As Exception 
        Console.WriteLine("Ausnahme: " & ex.Message) 
      End Try 
      Dim ap As IAufräumen(Of Profi) = b

      b.Tun() : s.Tun() : ap.Tun() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Hinweis
Die generischen und normalen Schnittstellen gemeinsamen Aspekte, wie zum Beispiel die Notwendigkeit, Standardwerte optionaler Parameter beizubehalten, werden hier nicht behandelt, sondern sind in Abschnitt 3.15, »Schnittstellen: Interface und Implements«, beschrieben.


Funktionszeiger (Delegate)

Sowohl normale als auch generische Delegates können auf generische oder normale Methoden zeigen. Wichtig ist, dass die Datentypen der Delegates zu denen der Methoden passen. Wenn für generische Methoden bei der Verknüpfung des Delegates mit einer generischen Methode keine Typparameter angegeben werden, kann der Compiler die Typen meist automatisch ermitteln, vorausgesetzt, die Compileroption Infer ist auf On gesetzt. Dies geht so weit, dass selbst der Typ der Rückgabe betrachtet wird, auch wenn dieser gar nicht zur Signatur der Methode gehört.

Im folgenden Codefragment werden ein nichtgenerisches Delegate GeldVerdienen und ein generische Delegate Lehren definiert. In der Methode Test() wird das nichtgenerische Delegate GeldVerdienen mit der generischen Methode Arbeiten() verknüpft, wobei der Compiler die Typisierung aufgrund des Rückgabetyps der Methode selbst ermittelt. Als Zweites zeigt das generische Delegate Lehren auf die nichtgenerische Methode Mathe(). Dann folgt der »normale« Fall einer Verbindung des generischen Delegates Lehren mit der generischen Methode Vertretung(). Schließlich werden die Delegates zur Kontrolle aufgerufen.


'...\Datentypen\Generisch\Infer.vb

Option Strict On 
Namespace Datentypen

  Delegate Function GeldVerdienen() As Double

  Delegate Sub Lehren(Of Stoff)(ByVal was As Stoff)

  Module Ableiten

    Function Arbeiten(Of Ergebnis)() As Ergebnis 
      Return Arbeiten                                   'Standardwert "Null" 
    End Function

    Sub Mathe(ByVal was As Integer) 
      Console.WriteLine("Diskussion der Zahl {0}", was) 
    End Sub

    Sub Vertretung(Of Wann)(ByVal w As Wann) 
      Console.WriteLine("Vertretung um {0}", w) 
    End Sub

    Sub Test()

      Dim schuften As GeldVerdienen = AddressOf Arbeiten 
      Dim reden As Lehren(Of Integer) = AddressOf Mathe 
      Dim beschäftigen As Lehren(Of Date) = AddressOf Vertretung(Of Date)

      Console.WriteLine("Verdienst: {0}", schuften.Invoke()) 
      reden.Invoke(Integer.MinValue) 
      beschäftigen.Invoke(Now)

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt die korrekte Verwendung der Methoden durch die Delegates:

Verdienst: 0 
Diskussion der Zahl –2147483648 
Vertretung um 3/14/2008 3:21:00 PM

Rheinwerk Computing - Zum Seitenanfang

4.4.4 Sichtbarkeit Zur nächsten ÜberschriftZur vorigen Überschrift

Welches Konstrukt auch immer einen Datentyp verwendet, es muss mindestens so sichtbar sein wie der verwendete Datentyp. Generische Datentypen bilden da keine Ausnahme. Ungewohnt ist lediglich, dass mehr Datentypen als sonst im Einsatz sind, so dass es leichter passieren kann, ein Problem zu übersehen. Das folgende Codefragment zeigt zwei Methoden, die beide eine Fehlermeldung des Compilers verursachen, und zwar durch einen Datentyp für einen Typparameter, der weniger sichtbar ist als die öffentliche Methode.


'...\Datentypen\Generisch\Sichtbarkeit.vb

Option Strict On 
Namespace Datentypen

  Public Class Ware(Of Typ, Menge) 
    Public Was As Typ 
    Public Wieviel As Menge 
  End Class

  Friend Class Boot : End Class

  Public Module Sichtbarkeit

    Friend Class Anwendung : End Class

    Sub Umgehung(Of P As Anwendung)(ByVal arg As P)        'Compilerfehler!! 
    End Sub

    Function Umgehung(Of W As Ware(Of Boot, Short))() As W 'Compilerfehler!! 
    End Function

  End Module 
End Namespace

Hinweis
Bitte beachten Sie, dass Datentypen auch ohne explizite Angabe immer eine Sichtbarkeit haben.



Rheinwerk Computing - Zum Seitenanfang

4.4.5 Bindung Zur nächsten ÜberschriftZur vorigen Überschrift

Typparameter generischer Datentypen legen nicht nur eine Vererbungshierarchie fest, sondern auch den Gültigkeitsbereich von mit Shared klassengebundenen Typmitgliedern. Verschiedene konkrete Typen für einen Typparameter erzeugen Mitglieder, die unabhängig voneinander sind. Das nächste Codefragment definiert einen Konstruktor Shared Sub New() für die Klasse und ein Feld Anzahl, das im Objektkonstruktor Sub New() inkrementiert wird. In Test() werden mehrere Objekte verschiedener konkreter Typen erzeugt und wird die Variable Anzahl für jeden der Typen ausgegeben.


'...\Datentypen\Generisch\Bindung.vb

Option Strict On 
Namespace Datentypen

  Class Hund : End Class 
  Class Katze : End Class

  Class Tier(Of Typ)

    Shared Sub New() 
      Console.WriteLine("Klasseninitialisierer {0}", GetType(Typ).Name) 
    End Sub

    Friend Shared Anzahl As Short

    Sub New() 
      Anzahl += 1 
    End Sub

  End Class

  Module Bindung 
    Sub Test()

      Dim hunde() As Tier(Of Hund) = New Tier(Of Hund)() _ 
        {New Tier(Of Hund), New Tier(Of Hund)} 
      Dim katzen() As Tier(Of Katze) = New Tier(Of Katze)() _ 
        {New Tier(Of Katze), New Tier(Of Katze), New Tier(Of Katze)}

      Console.WriteLine("{0} Hunde", Tier(Of Hund).Anzahl) 
      Console.WriteLine("{0} Katzen", Tier(Of Katze).Anzahl)

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Sowohl der Klassenkonstruktor als auch die klassengebundene Variable sind für jeden der Typen unterschiedlich.

Klasseninitialisierer Hund 
Klasseninitialisierer Katze 
2 Hunde 
3 Katzen

Rheinwerk Computing - Zum Seitenanfang

4.4.6 Rekursive Typen Zur nächsten ÜberschriftZur vorigen Überschrift

Typparameter können in ihren Einschränkungen auch sich selbst enthalten. Dies ist in solchen Fällen sinnvoll, wenn Methoden nur dann sinnvoll arbeiten können, wenn ihre Parameter zum selben Typ gehören. Durch die Formulierung der Typisierung im Typparameter kann der Compiler bereits eine typfremde Verwendung unterbinden. Das nächste Codefragment zeigt eine Schnittstelle zum Vergleich von Objekten desselben Typs. Der Schnittstellentyp inklusive Typparameter taucht in der As-Klausel auf. Die Implementation der Schnittstelle in der Klasse Wort verwendet den Vergleich von Zeichenkettenlängen als Kriterium der Gleichheit. In Test() wird die Methode über eine Schnittstellenreferenz auf IGleich(Of Wort) getestet.


'...\Datentypen\Generisch\Rekursiv.vb

Option Strict On 
Namespace Datentypen

  Interface IGleich(Of Typ As IGleich(Of Typ)) 
    Function Gleich(ByVal objekt As Typ) As Boolean 
  End Interface

  Class Wort : Implements IGleich(Of Wort) 
    Private zeichen As String 
    Sub New(ByVal zeichen As String) 
      Me.zeichen = zeichen 
    End Sub

    Public Function Gleichartig(ByVal w As Wort) As Boolean _ 
    Implements IGleich(Of Wort).Gleich 
      Return zeichen.Length = w.zeichen.Length 
    End Function

  End Class

  Module Rekursiv 
    Sub Test() 
      Dim r As IGleich(Of Wort) = New Wort("irgendetwas")

      Console.WriteLine("""Gleich"" {0}", r.Gleich(New Wort("gleich lang"))) 
      Console.WriteLine("""Gleich"" {0}", r.Gleich(New Wort("anders")))

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt die korrekte Arbeitsweise:

"Gleich" True 
"Gleich" False

Rheinwerk Computing - Zum Seitenanfang

4.4.7 Generische Typen in .NET topZur vorigen Überschrift

In .NET begegnen Ihnen generische Typen wahrscheinlich als Erstes im Gewand von Datensammlungen, englisch Collections. Aufgrund des großen Umfangs werden sie in Kapitel 6, »Collections und LINQ«, behandelt. Es gibt noch sehr viel mehr Stellen, an denen generische Typen zum Einsatz kommen. Exemplarisch greife ich hier nur zwei Arten heraus: Vergleiche und Funktionsobjekte.

Vergleiche

Die Vielfalt in der Objektorientierung macht es unmöglich, ohne weitere Kenntnisse Objekte zu vergleichen. Daher stützen sich Vergleichsfunktionen auf typspezifische Schnittstellen. Die Allgemeinheit bleibt durch die Verwendung generischer Typen gewahrt.

System-+IComparable(Of T) 
       +IEquatable(Of T) 
System.Collections.Generic-+IComparer(Of T) 
                           +IEqualityComparer(Of T)

Die Nutzung der Schnittstellen ist sehr einfach. Dazu müssen Sie in Ihren Klassen Zustände identifizieren, die vom Charakter her vergleichbar sind. In der Rhetorik wird dies als tertium comparationis bezeichnet, übersetzt »ein Drittes zum Vergleich«. Hier sind die ersten beiden Zwei die verschiedenen Klassen, das Dritte der oder die zu vergleichenden Zustände. Damit lassen sich beliebige Objekte miteinander vergleichen. Was Sie für den Vergleich heranziehen, hängt von der Problemstellung ab. Das folgende Beispiel zeigt den Vergleich eines Apfeldurchmessers mit einem Birnengewicht. Inhaltlich ist das Blödsinn, es zeigt aber die Tragweite des Konzepts. Laut der Dokumentation zur Schnittstelle IComparable muss die Methode CompareTo() einen negativen Wert liefern, wenn das Objekt »kleiner« als das Vergleichsobjekt ist, und einen positiven, wenn es »größer« ist. Bei Gleichheit wird die Zahl 0 zurückgegeben.


'...\Datentypen\Generisch\Vergleich.vb

Option Strict On 
Namespace Datentypen

  Class Apfel : Implements IComparable(Of Birne) 
    Friend Durchmesser As Double 
    Sub New(ByVal d As Double) 
      Durchmesser = d 
    End Sub 
    Function CompareTo(ByVal b As Birne) As Integer _ 
    Implements IComparable(Of Birne).CompareTo 
      If(b Is Nothing, 1, Durchmesser.CompareTo(b.Gewicht)) 
    End Function 
  End Class

  Class Birne : Implements IComparable(Of Apfel) 
    Friend Gewicht As Double 
    Sub New(ByVal g As Double) 
      Gewicht = g 
    End Sub 
    Function CompareTo(ByVal a As Apfel) As Integer _ 
    Implements IComparable(Of Apfel).CompareTo 
      Return If(a Is Nothing, 1, Gewicht.CompareTo(a.Durchmesser)) 
    End Function 
  End Class

  Module Vergleich 
    Sub Test() 
      Dim a As New Apfel(80), b As New Birne(75) 
      Console.WriteLine("Apfel "">"" Birne: {0}", a.CompareTo(b) > 0) 
      Console.WriteLine("Birne "">"" Apfel: {0}", b.CompareTo(a) > 0) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Nun lassen sich sogar Äpfel und Birnen vergleichen:

Apfel ">" Birne: True 
Birne ">" Apfel: False

Auf die Gleichheit zweier Objekte gehe ich in Abschnitt 10.1, »Object«, ein.

Funktionsobjekte

Die Vererbungslinie Object PfeilR ActivationContext PfeilR Delegate PfeilR MulticastDelegate im Namensraum System hat einige interessante Delegates. Prozeduren ohne und Funktionen mit Rückgabe werden mit null bis vier Parametern durch folgende Typen repräsentiert (optionale Teile stehen in eckigen Klammern):

Action[(Of T1[,T2[,T3[,T4]]])] 
Func(Of [T1,[T2,[T3,[T4,]]]] TErgebnis)

Dadurch müssen Sie dafür nicht selbst Delegates definieren. Für Ereignisse steht das Delegate EventHandler(Of TEventArgs As {EventArgs}) zur Verfügung, zum Vergleich Comparison (Of T) und Predicate(Of T) sowie Converter(Of TInput,TOutput) zur Typumwandlung.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual Basic 2008
Visual Basic 2008
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Visual Basic 2012






 Visual Basic 2012


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern