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

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 3 Klassendesign
Pfeil 3.1 Objektorientierung
Pfeil 3.1.1 Einführung
Pfeil 3.1.2 Vorteile
Pfeil 3.1.3 Klassenimplementierung in .NET
Pfeil 3.1.4 Klassen in Visual Basic
Pfeil 3.1.5 Projekttyp »Klassenbibliothek«
Pfeil 3.1.6 Bemerkung zu den Codefragmenten
Pfeil 3.1.7 Objekte durch New
Pfeil 3.1.8 Ausnahmen mit Throw auslösen
Pfeil 3.1.9 Datentypen
Pfeil 3.1.10 Sichtbarkeit der Klasse
Pfeil 3.1.11 Aufteilung der Definition mit Partial
Pfeil 3.1.12 Grafikbibliothek: Beispiel für Kapitel 3 und 4
Pfeil 3.2 Kapselung
Pfeil 3.2.1 Kombinationen
Pfeil 3.2.2 Private
Pfeil 3.2.3 Sichtbarkeitsmodifikatoren
Pfeil 3.2.4 Lokale Variablen
Pfeil 3.2.5 Softwareschutz
Pfeil 3.2.6 Grafikbibliothek: private Größe des Rechtecks
Pfeil 3.3 Verhalten (Methoden)
Pfeil 3.3.1 Prinzip
Pfeil 3.3.2 Verlassen der Methode
Pfeil 3.3.3 Parameter
Pfeil 3.3.4 Überladung (Overloads)
Pfeil 3.3.5 Rückgabewert
Pfeil 3.3.6 Reine Deklaration mit Partial
Pfeil 3.3.7 Grafikbibliothek: Zugriffsmethoden auf die Größe des Rechtecks
Pfeil 3.4 Bindung
Pfeil 3.4.1 Klassenbindung mit Shared
Pfeil 3.4.2 Klassenkonstruktoren
Pfeil 3.4.3 Externe Funktionen
Pfeil 3.4.4 Grafikbibliothek: Rechteckübergreifendes
Pfeil 3.5 Objektinitialisierung mit Konstruktoren
Pfeil 3.5.1 Objektkonstruktoren
Pfeil 3.5.2 Nichtöffentliche Konstrukturen
Pfeil 3.5.3 Grafikbibliothek: Initialisierung des Rechtecks
Pfeil 3.6 Zustände (Felder)
Pfeil 3.6.1 Deklaration und Initialisierung
Pfeil 3.6.2 Sichtbarkeit
Pfeil 3.6.3 Konstanten: ReadOnly und Const
Pfeil 3.6.4 With
Pfeil 3.6.5 Grafikbibliothek: Konstanten des Rechtecks
Pfeil 3.7 Eigenschaften
Pfeil 3.7.1 Kontrolle beim Aufrufer
Pfeil 3.7.2 Zugriffsmethoden
Pfeil 3.7.3 Getter und Setter: Property
Pfeil 3.7.4 Indexer
Pfeil 3.7.5 Standardeigenschaft: Default
Pfeil 3.7.6 Schreibschutz und Leseschutz: ReadOnly und WriteOnly
Pfeil 3.7.7 Sichtbarkeit
Pfeil 3.7.8 Klammern
Pfeil 3.7.9 Grafikbibliothek: Eigenschaften des Rechtecks
Pfeil 3.8 Innere Klassen
Pfeil 3.8.1 Beziehung zur äußeren Klasse
Pfeil 3.8.2 Sichtbarkeit
Pfeil 3.8.3 Grafikbibliothek: Position des Rechtecks
Pfeil 3.9 Dynamisches Verhalten: Delegate und Function
Pfeil 3.9.1 Funktionszeiger: Delegates
Pfeil 3.9.2 Automatisch generierter Code
Pfeil 3.9.3 Mehrere Aktionen gleichzeitig
Pfeil 3.9.4 Asynchrone Aufrufe
Pfeil 3.9.5 Funktionsobjekte: Function (λ-Ausdrücke)
Pfeil 3.9.6 Umwandlungen
Pfeil 3.9.7 Grafikbibliothek: Vergleich von Rechtecken
Pfeil 3.10 Ereignisse
Pfeil 3.10.1 Ereignis: Event und RaiseEvent
Pfeil 3.10.2 Statische Methodenbindung WithEvents und Handles
Pfeil 3.10.3 Dynamische Methodenbindung: AddHandler und RemoveHandler
Pfeil 3.10.4 Benutzerdefinierte Ereignisse: Custom Event
Pfeil 3.10.5 Umwandlungen
Pfeil 3.10.6 Grafikbibliothek: Größenänderungen von Rechtecken überwachen
Pfeil 3.11 Benutzerdefinierte Operatoren
Pfeil 3.11.1 Prinzip
Pfeil 3.11.2 Überladung
Pfeil 3.11.3 Vergleich
Pfeil 3.11.4 Typumwandlung mit CType: Widening und Narrowing
Pfeil 3.11.5 Wahrheitswerte: IsTrue und IsFalse
Pfeil 3.11.6 Grafikbibliothek: Addition und Umwandlung von Rechtecken
Pfeil 3.12 Alle Klassenelemente
Pfeil 3.12.1 Der Namensraum My
Pfeil 3.13 Vererbung
Pfeil 3.13.1 Klassenbeziehung durch Inherits
Pfeil 3.13.2 Sichtbarkeitsmodifikatoren
Pfeil 3.13.3 Zugriff auf Eltern mit MyBase
Pfeil 3.13.4 Modifikation: Shadows und Overloads (Overrides)
Pfeil 3.13.5 Abstrakte Klassen: MustInherit und MustOverride
Pfeil 3.13.6 Spezifische Catch-Blöcke
Pfeil 3.13.7 Eigene Ausnahmen
Pfeil 3.13.8 Arrays
Pfeil 3.13.9 Grafikbibliothek: neue Vielecke
Pfeil 3.14 Polymorphie
Pfeil 3.14.1 Virtuelle Methoden: Overridable und Overrides
Pfeil 3.14.2 Unterbrechen: Overloads, Shadows und Overrides
Pfeil 3.14.3 Unterbinden: NotInheritable, NotOverridable und MyClass
Pfeil 3.14.4 Konstruktoren
Pfeil 3.14.5 Equals
Pfeil 3.14.6 ToString
Pfeil 3.14.7 Grafikbibliothek: Objektsammlungen
Pfeil 3.15 Schnittstellen: Interface und Implements
Pfeil 3.15.1 Benennungen und Parameter
Pfeil 3.15.2 Schnittstellen und Vererbung
Pfeil 3.15.3 Schnittstellenvererbung
Pfeil 3.15.4 Schnittstelle oder abstrakte Klasse?
Pfeil 3.15.5 Grafikbibliothek: Flächen
Pfeil 3.16 Lebensende eines Objekts
Pfeil 3.16.1 Garbage Collector
Pfeil 3.16.2 Destruktoren
Pfeil 3.16.3 Dispose und Using


Rheinwerk Computing - Zum Seitenanfang

3.9 Dynamisches Verhalten: Delegate und Function Zur nächsten ÜberschriftZur vorigen Überschrift

»Drum prüfe, wer sich ewig bindet, ob sich nicht etwas Bess‘res findet.«

Bisher haben wir das Verhalten eines Objekts im Voraus festgelegt, indem wir Methoden in der zum Objekt gehörigen Klasse definiert haben. Dies erlaubt dem Compiler, die Konsistenz von Methodendefinition und -gebrauch zu prüfen. So komfortabel es ist, sich vom Compiler bei der Fehlersuche helfen zu lassen, manchmal ist das Korsett einfach zu eng. In solchen Situationen ist es wünschenswert, erst zur Laufzeit zu entscheiden, wie sich das Objekt verhält. Zum Beispiel wird bei der Anweisung, sich nach Amerika zu begeben, die Wahl nicht primär auf das Fahrrad fallen. Manchmal steuert auch der Zustand eines Objekts das Verhalten; zum Beispiel fällt das Auto bei Fahruntüchtigkeit des Fahrers aus.

Eine Möglichkeit, das Verhalten erst während der Laufzeit festzulegen, besteht in der Verwendung geeigneter Kontrollstrukturen (If, Select, …). Diese Vorgehensweise hat entscheidende Nachteile. Wenn eine Verhaltensweise dazukommt, muss der Quelltext angepasst werden. Außerdem wird durch die vielen Anweisungen der Code recht »sperrig«. Daher bietet Visual Basic die Möglichkeit, Variablen zu definieren, in denen die zu verwendende Funktionalität gespeichert ist. Dadurch kann mit einer einfachen Variablenzuweisung das Verhalten geändert werden. Visual Basic bietet zwei Möglichkeiten:

  • Funktionszeiger: Referenz auf eine Methode
  • Funktionsobjekt: Anweisungen werden in der Variablen gespeichert.

Rheinwerk Computing - Zum Seitenanfang

3.9.1 Funktionszeiger: Delegates Zur nächsten ÜberschriftZur vorigen Überschrift

Wenden wir uns nun den Funktionszeigern zu. Da sie nur eine Referenz darstellen und die eigentliche Aufgabe an die Funktion weiterreichen, auf die sie zeigen, werden sie auch Delegates genannt. Ihr Einsatz besteht aus vier Teilen:

  • 1 Deklaration des Typs der zu verwendenden Funktion
  • 2 Deklaration der Variablen, über die die Funktion angesprochen wird
  • 3 Wertbelegung der Variablen, d. h. Verbindung zur konkreten Funktion
  • 4 Aufruf der Funktion über die Variable

Der erste Punkt ist einer Methodendeklaration sehr ähnlich. In der Syntax macht das Wort Delegate aus einer Methodendeklaration einen Funktionszeiger. Der Name in der Deklaration ist nur formell ein Methodenname und vereinbart in Wirklichkeit einen neuen Datentyp, der den Typ der zu verwendenden Funktion vollständig beschreibt. Analog zu Methoden kann mit Sub eine Prozedur ohne Rückgabewert und mit Function eine Funktion mit Rückgabewert vereinbart werden. Optionale Teile sind in eckige Klammern gesetzt, kursive Teile müssen Sie Ihren Bedürfnissen anpassen.


[<Sichtbarkeit>] Delegate Sub Name([<Parameter>]) 1
[<Sichtbarkeit>] Delegate Function Name([<Parameter>]) As Typ


Hinweis
Da ein Delegate implizit einen neuen Datentyp vereinbart, kann die Deklaration auch außerhalb einer Klasse stehen, direkt innerhalb eines Namensraums.


Tabelle 3.13 zeigt die erlaubten Modifikatoren.


Tabelle 3.13 Modifikatoren eines Delegates

Art Beschreibung

Sichtbarkeit

Grad der Öffentlichkeit (siehe Abschnitt 3.2, »Kapselung«)

Redefinition

Ersatzes mit Shadows (siehe Abschnitt 3.13, »Vererbung«)



Hinweis
Optionale Parameter (Optional oder ParamArray) sind nicht erlaubt, und sowohl die Parametertypen als auch ein eventueller Rückgabetyp müssen mindestens den gleichen Grad der Öffentlichkeit haben wie der Funktionszeiger selbst.


Der zweite Punkt unterscheidet sich nicht von einer normalen Variablendeklaration. Als Typ wird Name aus der Deklaration des Delegates verwendet. Die Modifikatoren für lokale Variablen sind in Abschnitt 2.5.3, »Variablendeklaration«, beschrieben, die Modifikatoren für Variablen auf Klassenebene in Abschnitt 3.6.1, »Deklaration und Initialisierung«.


[<Modifikatoren>] var As Name 2

Als dritter Punkt muss der Variablen vor ihrer Verwendung noch ein Wert zugewiesen werden. Dieser besteht aus der Adresse der Funktion, die von der Variablen repräsentiert wird, oder aus einem expliziten Funktionsobjekt. Auf Funktionsobjekte gehen wir in Abschnitt 3.9.5, »Funktionsobjekte: Function (λ-Ausdrücke)«, ein. Die Modifikatoren der Methode haben keinerlei Einfluss auf die spätere Verwendung des Funktionszeigers. Zum Beispiel kann die Methode durch den Modifikator Private nur in der Klasse nutzbar sein und dennoch durch einen öffentlichen Funktionszeiger von außen (indirekt) benutzbar sein. Zur Zuweisung stehen die vier folgenden Syntaxvarianten zur Verfügung (optionale Teile stehen in eckigen Klammern und kursive Teile müssen Sie Ihren Bedürfnissen anpassen):


var = New Name(AddressOf methode) 3 var = New Name(Function([<Parameter>]) Anweisung) var = AddressOf methode var = Function([<Parameter>]) Anweisung


Hinweis
Oft ist eine Qualifizierung der Methode durch ein Objekt oder eine Klasse notwendig (objekt.methode oder klasse.methode).


Schließlich wird die Variable verwendet, um den eigentlichen Aufruf durchzuführen. Auch hier gibt es mehrere Varianten (die kursiven Teile müssen Sie Ihren Bedürfnissen anpassen). Die zweite ist die Langform der ersten. Auf die dritte gehen wir in Abschnitt 3.9.4, »Asynchrone Aufrufe«, ein.


var([<Parameter>]) 4 var.Invoke([<Parameter>]) var.BeginInvoke([<Parameter>])

Prinzip

Schauen wir uns zuerst ein Beispiel an, das alle vier Teile des Einsatzes eines Funktionszeigers enthält. Der direkte Aufruf der Funktion wäre sicherlich einfacher; es geht hier nur um die Demonstration der Syntax.


'...\Klassendesign\Funktionen\Delegate.vb

Option Explicit On 
Namespace Klassendesign 
  Module Einfach

    Delegate Function Aktion(ByVal x As Short) As Short '1

    Function Quadrat(ByVal x As Short) As Short 
      Return x * x 
    End Function

    Sub Test() 
      Dim akt As Aktion                                 '2 
      akt = AddressOf Quadrat                           '3 
      Console.WriteLine("{0}^2 = {1}", 4, akt(4))       '4 
      Console.ReadLine() 
    End Sub

  End Module 
End Namespace

Die vier Teile sind gekennzeichnet. Erstens wird mit Delegate ein Funktionszeiger Aktion deklariert. Als Zweites wird die Variable akt vom Typ Aktion deklariert. Im dritten Schritt wird die Variable mit der Funktion Quadrat verbunden. Schließlich erfolgt im vierten Schritt der Aufruf von Quadrat(4) mittels akt(4). Die Ausgabe bestätigt den korrekten Ablauf des Programms:

4^2 = 16

Listen

Das vorige Beispiel hat noch nicht demonstriert, worin der Vorteil des Einsatzes von Funktionszeigern besteht. Demgegenüber nutzt das folgende Codefragment einen Funktionszeiger als Parameter der Funktion Scan, um den Code, der zum Durchlaufen der Liste nötig ist, nur einmal schreiben zu müssen. Denn egal, welche Funktion auf die Elemente der Liste angewendet wird, die Write-Methoden zum Ausdruck und ein Schleifenkonstrukt zur Erfassung aller Listenelemente werden immer gebraucht. An die Stelle der expliziten Wertzuweisung der Variablen tritt die Parameterübergabe an Scan, die die Zuweisung implizit vornimmt. Der Vollständigkeit halber sind alle vier Varianten der Belegung gezeigt; auf Function gehen wir in Kürze ein (siehe Abschnitt 3.9.5, »Funktionsobjekte: Function (λ-Ausdrücke)«).


‘ ...\Klassendesign\Funktionen\DelegateListen.vb

Option Explicit On 
Namespace Klassendesign 
  Module Listen

    Delegate Function Aktion(ByVal x As Double) As Double

    Sub Scan(ByVal name As String, ByVal fun As Aktion, ByVal x() As Single) 
      Console.Write(name & " ") 
      For Each v As Single In x 
        Console.Write("{0,5}", fun(v).ToString("#0.0")) 
      Next 
      Console.WriteLine() 
    End Sub

    Sub Test() 
      Dim v(4) As Single 
      For no As Int32 = 0 To v.Length-1 : v(no)=no+1 : Next 
      Scan("Sinus    ", New Aktion(AddressOf Math.Sin), v) 
      Scan("Identität", New Aktion(Function(x) x), v) 
      Scan("Wurzel   ", AddressOf Math.Sqrt, v) 
      Scan("Quadrat  ", Function(x) x ^ 2, v) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass alle Funktionen korrekt angesprochen wurden:

Sinus       0.8  0.9  0.1 –0.8 –1.0 
Identität   1.0  2.0  3.0  4.0  5.0 
Wurzel      1.0  1.4  1.7  2.0  2.2 
Quadrat     1.0  4.0  9.0 16.0 25.0

Damit dies möglich ist, werden die Single-Elemente der Variablen v implizit in einen Double umgewandelt, also den Typ, den das Delegate laut Deklaration erwartet.


Rheinwerk Computing - Zum Seitenanfang

3.9.2 Automatisch generierter Code Zur nächsten ÜberschriftZur vorigen Überschrift

Durch die Deklaration eines Delegates wird vom Compiler implizit eine neue Klasse gleichen Namens definiert, die auf der Klasse MultiCastDelegate basiert. Das folgende Listing enthält nur die für Ihren Code zugänglichen Klassenmitglieder, ohne Modifikatoren anzugeben. Um das Listing weiter zu kürzen, sind einige Methodensignaturen mit optionalen Parametern (auch solchen, die nicht am Ende stehen) und Alternativen mit einem vertikalen Strich | formuliert. Redundante Signaturen sind auch weggelassen worden (sie existieren zur Beschleunigung). Der Wert params steht für die Parameter, die Sie dem Delegate im Quelltext gegeben haben. Alle nicht gekennzeichneten Parameter werden als Wert übergeben (ByVal).

Class klasse 
  Class delegat 
    Function BeginInvoke(<params (alle ByVal)>, _ 
      rückruf As AsyncCallback, status As Object) _ 
      As IAsyncResult 
    Function EndInvoke(<nur params mit ByRef>, _ 
      DelegateAsyncResult As IAsyncResult) As Double 
    Function Invoke(<params>) As Double

'Class MulticastDelegate 
    Sub GetObjectData(info As SerializationInfo, cont As StreamingContext) 
    Function Equals(obj As Object) As Boolean 
    Function GetInvocationList() As Delegate() 
    Operator =|<>(d1 As MulticastDelegate, d2 As MulticastDelegate) _ 
      As Boolean 
    Function GetHashCode() As Integer

'Class Delegate 
    Function Clone() As Object 
    Function DynamicInvoke(ParamArray argumente As Object()) As Object 
    Shared Function Combine(ParamArray delegaten As Delegate()) As Delegate 
    Shared Function Remove|RemoveAll(quelle As Delegate, wert As Delegate) _ 
      As Delegate 
    Shared Function CreateDelegate( _ 
      typ As Type, ziel As Object|Type, methode As String, _ 
      Optional grossSchreibungIgnorieren As Boolean, _ 
      Optional throwBindungsfehler As Boolean) As Delegate 
    Shared Function CreateDelegate(typ As Type, _ 
      Optional erstesArg As Object, methode As MethodInfo, _ 
      Optional throwBindungsfehler As Boolean) As Delegate 
    Operator =|<>(d1 As Delegate, d2 As Delegate) As Boolean 
    ReadOnly Property Method() As MethodInfo 
    ReadOnly Property Target() As Object 
'Class Object 
  End Class 
End Class

Rheinwerk Computing - Zum Seitenanfang

3.9.3 Mehrere Aktionen gleichzeitig Zur nächsten ÜberschriftZur vorigen Überschrift

Es gibt Situationen, in denen sich eine Aktion aus mehreren Teilen zusammensetzt, die zusammen ausgeführt werden müssen. Zum Beispiel muss bei einer Kontobuchung gewährleistet sein, dass die Summe aller Buchungen null ist, da sonst entweder Geld aus dem Nichts geschaffen würde oder Geld verschwinden würde. Das folgende Codefragment zeigt eine Überweisung von einem Konto auf ein anderes. Damit alle Kontobewegungen gemeinsam erfolgen, werden sie in einem einzelnen Delegate durch die Methode Combine in der Variablen um gebündelt. Innerhalb von Buchung() wird durch die Referenzübergabe der summe eine Anpassung der Überweisungsrichtung möglich.


'...\Klassendesign\Funktionen\MehrfachAktionen.vb

Option Explicit On 
Namespace Klassendesign

  Class Konto 
    Shared nr As Integer 
    Private no As Integer 
    Private euro As Double 
    Sub New(ByVal stand As Double) 
      nr += 1 : no = nr : euro = stand 
    End Sub 
    Sub Buchen(ByRef summe As Double) 
      euro += summe : summe *= –1 'ab jetzt abziehen 
      Console.Write("Konto {0}: {1} Euro   ", no, euro) 
    End Sub 
  End Class

  Module Bank 
    Delegate Sub Buchung(ByRef summe As Double) 
    Sub Test() 
      Dim k1 As Konto = New Konto(1000) 
      Dim k2 As Konto = New Konto(1000) 
      Dim b1 As Buchung = AddressOf k1.Buchen 
      Dim b2 As Buchung = AddressOf k2.Buchen 
      Dim um As Buchung = CType([Delegate].Combine(b1, b2), Buchung) 
      um(300) : Console.WriteLine() 'alles inklusive 
      um(-100) : Console.WriteLine() 'alles inklusive 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Hinweis
Combine ignoriert Nullreferenzen (Nothing).


Die Ausgabe zeigt, wie sich die Buchungen jeweils auf beide Konten auswirken:

Konto 1: 1300 Euro   Konto 2: 700 Euro 
Konto 1: 1200 Euro   Konto 2: 800 Euro

Hinweis
Durch die Bündelung von Aktionen kann kein Teil einer zusammengesetzten Aktion vergessen werden. Die Reihenfolge der Aktionen bleibt auch in der Kombination gewahrt.



Rheinwerk Computing - Zum Seitenanfang

3.9.4 Asynchrone Aufrufe Zur nächsten ÜberschriftZur vorigen Überschrift

Normalerweise werden die Anweisungen in einer Methode in der Reihenfolge ausgeführt, die der Quelltext vorgibt. Dies ist nicht immer erwünscht. Es gibt Situationen, in denen Teile eines Programms unabhängig agieren sollen. Ein solches Beispiel ist die Interaktion zweier Programmteile, deren Programmflüsse unabhängig voneinander sind. Bitte beachten Sie, dass nur die Abläufe der Teile nichts miteinander zu tun haben. Es ist ohne Weiteres möglich, aber nicht zwingend, dass die Teile mit denselben Daten arbeiten und dadurch miteinander kommunizieren. Denn ändert ein Teil eine gemeinsam genutzte Variable, ist dies im anderen Teil natürlich sichtbar (er benutzt identisch dieselbe Variable). Der logische »Kleber« der Teile ist also der Satz an Daten, auf die beide Teile Zugriff haben.

Ein Funktionszeiger bietet die Möglichkeit, die mit ihm verbundene Funktion so aufzurufen, dass sie unabhängig von der Methode ist, von der sie aufgerufen wurde. Die aufgerufene Methode ist damit von den Anweisungen der aufrufenden Methode entkoppelt. Solche unabhängigen Programmfäden werden im Englischen Threads genannt. Die Unabhängigkeit der Threads wird durch das (Betriebs)System gewährleistet. Für die Programmlogik spielt es keine Rolle, ob die verschiedenen Teile auf physisch unabhängiger Hardware (Prozessorkernen) laufen oder ob das System die Teile logisch dadurch entkoppelt, dass ihnen abwechselnd kleine Zeiteinheiten auf einem einzelnen Prozessor zur Verfügung gestellt werden.


Hinweis
In diesem Abschnitt erfolgt nur ein kurzer Einstieg in die Thematik, der mit Delegates zusammenhängt. Das Kapitel 5, »Multithreading«, befasst sich ausführlich mit dem Themenkreis.


Im folgenden Codefragment wird in der Methode Los() so lange hochgezählt und die Zahlen ausgegeben, bis weiter von außen auf False gesetzt wird. Damit diese Einflussnahme von außen möglich ist, muss dieses »Außen« dadurch geschaffen werden, dass die Methode Los() unabhängig vom anderen Programmfluss läuft. Dies wird durch den Start über BeginInvoke() erreicht.

Die Reihenfolge des Programmflusses ist durch Zahlen kenntlich gemacht, die beiden unabhängigen Programmteile (Threads) durch die Buchstaben A und B. Die logische Reihenfolge des Programmablaufs lautet:

1A Zuerst wird die Delegate-Variable zähl mit der Methode Los() verbunden.
2A Diese wird in Test() durch BeginInvoke() unabhängig von der Anweisungsreihenfolge in Test() gestartet. Die ersten beiden Parameter werden an Los() als Werte übergeben, als seien alle Parameter mit ByVal deklariert worden. Die Berücksichtigung von ByRef erfolgt im Schritt 3B. Der vorletzte Parameter ist ein Funktionszeiger auf die nach Beendigung von Los() aufzurufende Methode. Der letzte Parameter nimmt Zusatzinformationen für eben jene Routine auf.
3A Dann wird ReadLine() aufgerufen, und die Anweisungsfolge in Test() blockiert, bis ein Zeilenvorschub eingegeben wird.
1B In der Zwischenzeit wird die While-Schleife in Los() ausgeführt. Der Sleep-Befehl sorgt dafür, dass die Zahlen in einer Geschwindigkeit erscheinen, in der Menschen sie lesen können.
4A Nach Eingabe des Zeilenvorschubs wird in Test() die Variable weiter gesetzt, und mit einem erneuten ReadLine() wird der weitere Fortschritt blockiert.
2B Im parallel laufenden Los() wird daraufhin die While-Schleife verlassen und die Methode beendet.
3B Danach wird automatisch die in BeginInvoke() als vorletzter Parameter spezifizierte Funktion Ende() aufgerufen. In dieser wird mit EndInvoke() das Ergebnis abgeholt. Außerdem werden alle ByRef-Parameter in der Reihenfolge an EndInvoke() übergeben, die im Delegate deklariert ist. EndInvoke() belegt sie mit den Werten, die bei Beendigung von Los() gültig waren. Schließlich wird mit AsyncState auf den letzten Parameter von BeginInvoke() zugegriffen.
5A Zuletzt beendet ein Zeilenvorschub Test() und damit das ganze Programm.

Die Formulierung ohne unabhängige Teile ist hier nicht möglich, da jeder Versuch, mit ReadLine() eine Benutzereingabe zu lesen, den Programmablauf blockiert.


'...\Klassendesign\Funktionen\Asynchron.vb

Option Explicit On 
Namespace Klassendesign 
  Module Auswahl 
    Function Los(ByVal delta As Int32, ByRef anz As Int32) As Int32 '1B 
      Dim no As Integer 
      While weiter 
        anz += 1 : no += delta : Console.Write("{0} ", no) 
        Threading.Thread.Sleep(500) '=> Zeit zum Lesen 
      End While 
      Return no                                                     '2B 
    End Function 
    Delegate Function ZählFun( _ 
      ByVal delta As Int32, ByRef anläufe As Int32) As Int32 
    Public zähl As ZählFun = AddressOf Los                          '1A 
    Public weiter As Boolean = True 'gemeinsam (!) genutzt 
    Sub Test() 
      Dim res As IAsyncResult = _ 
        zähl.BeginInvoke(2, 0, AddressOf Ende, "Test")              '2A 
      Console.ReadLine() 'blockieren                                 3A 
      weiter = False     '=> Ende von While in Los                   4A 
      Console.ReadLine()                                            '5A 
    End Sub 
    Sub Ende(ByVal ar As IAsyncResult)                              '3B 
      Dim versuche As Int32 
      Dim z As Int32 = zähl.EndInvoke(versuche, ar) 
      Console.WriteLine("{0} nach {1} Zahlen", z, versuche) 
      Console.WriteLine("Start in {0}", ar.AsyncState) 
    End Sub 
  End Module 
End Namespace

Eine typische Ausgabe zeigt einige durch Los() ausgegebene Zahlen. Die beiden letzten Zeilen werden in Ende() erzeugt.

2 4 6 8 10 12 14 16 18 20 
20 nach 10 Zahlen 
Start in Test

Hinweis
Fehler in unabhängig laufenden Threads zu finden, kann recht schwierig sein. Lassen Sie sich davon nicht entmutigen. Das Konzept bietet viele Möglichkeiten, unter anderem bezüglich der Beschleunigung eines Programms durch die Ausnutzung mehrerer Prozessorkerne gleichzeitig. Praktisch jede grafische Benutzeroberfläche arbeitet mit verschiedenen Threads.



Rheinwerk Computing - Zum Seitenanfang

3.9.5 Funktionsobjekte: Function (λ-Ausdrücke) Zur nächsten ÜberschriftZur vorigen Überschrift

Visual Basic hat zwei Arten von Routinen, die etwas zurückgeben. Auf Modulebene werden sie Funktionen genannt und mit Function deklariert (siehe Abschnitt 3.3.5, »Rückgabewert«), als Wert einer Variablen nennt man sie λ-Ausdruck (Funktionsobjekt).

Dieser in einer Variablen gespeicherte λ-Ausdruck führt die funktionale Programmierung in .NET ein. Die Erklärung, was dies genau bedeutet, würde ein ganzes Buch füllen (für einen ersten Einstieg siehe http://de.wikipedia.org/wiki/Funktionale_Programmierung). In diesem Buch wird der praktische Nutzwert solcher Variablen exemplarisch gezeigt.

Die Syntax eines Function-Ausdrucks ist gegeben durch (optionale Teile stehen in eckigen Klammern und kursive Teile müssen Sie Ihren Bedürfnissen anpassen):


Function([<Parameter>]) <Ausdruck>

Der Ausdruck muss einen Wert zurückliefern. Ein Aufruf einer mit Sub deklarierten Prozedur, zum Beispiel von WriteLine, ist nicht erlaubt. Außerdem ist nur ein einzelner Ausdruck erlaubt. Der kann aber auch eine Funktion aufrufen.

Im folgenden Codefragment wird der Variablen sec ein Funktionsobjekt zugewiesen und in WriteLine als Funktion verwendet:


'...\Klassendesign\Funktionen\Lambda.vb

Option Strict On 
Namespace Klassendesign 
  Module Lambda 
    Sub Parameterlos() 
      Dim sec = Function() Now.Second 
      Console.WriteLine("Minutenanteil: {0}", sec() / 60) 
      Console.ReadLine() 
    End Sub 
    ... 
  End Module 
End Namespace

Im Gegensatz zu bisherigen Variablendeklarationen fällt auf, dass die As-Klausel fehlt. Dies liegt daran, dass der Compiler den Typ aus dem Funktionsobjekt automatisch ermitteln kann. Er tut dies, da standardmäßig Option Infer On gesetzt ist. In wenigen Fällen scheitert der Automatismus, und der Typ muss explizit angegeben werden. Bei den Delegates haben Sie bereits gesehen, wie ein solcher Typ angegeben wird. Beim folgenden Codefragment scheitert der Compiler bei der automatischen Typermittlung, und Sie müssen den Typ explizit spezifizieren.

Delegate Function IIFun(ByVal x As Int32) As Int32 
Dim fak As IIFun = Function(x As Int32) If(x > 1, x * fak(x – 1), 1)

Das folgende Codefragment zeigt, dass die Parametertypen wie gewohnt mit einer As-Klausel deklariert werden. Die Ausgaben der WriteLine-Befehle sind der Zeile als Kommentar angehängt.


'...\Klassendesign\Funktionen\Lambda.vb

Option Strict On 
Namespace Klassendesign 
  Module Lambda 
    ... 
    Sub Parameter() 
      Dim mult = Function(z1 As Double, z2 As Double) z1*z2 
      Console.WriteLine("4*3: {0}", mult(4, 3)) '4*3: 12 
      Console.WriteLine("5*7: {0}", mult(5, 7)) '5*7: 35 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Hinweis
Optionale Parameter (Optional und ParamArray) sind nicht erlaubt, aber Referenzen jeder Art.


Durch ByRef gekennzeichnete Parameter sind auch erlaubt. Sie sind nur dann nötig, wenn im Ausdruck des Funktionsobjekts eine Methode mit diesem Parameter aufgerufen wird, denn eine direkte Zuweisung eines Wertes im Funktionsobjekt ist nicht möglich, da eine Zuweisung keinen Wert an sich hat, sondern »nur« den Seiteneffekt einer Variablenänderung.

Funktionsobjekt als Parameter

Wie jeder andere Datentyp kann auch ein Funktionsobjekt als Parametertyp einer anderen Funktion dienen. Der Typ eines Funktionsobjekts wird in einer Delegate-Deklaration festgelegt. Im folgenden Codefragment werden auf dieselbe Zahl verschiedene Arten der Rundung angewandt. Die entsprechenden Funktionsobjekte a1 und a2 werden an eine allgemeine Rundungsfunktion etwa() als Parameter übergeben.


'...\Klassendesign\Funktionen\Parameter.vb

Option Strict On 
Namespace Klassendesign 
  Module Parameter

    Delegate Function EtwaFun(ByVal v As Double) As Double

    Sub Funktionsobjekt() 
      Dim etwa = Function(f As EtwaFun, x As Double) f(x) 
      Dim a1 As EtwaFun = AddressOf Math.Floor 
      Dim a2 As EtwaFun = AddressOf Math.Round 
      Console.WriteLine("Floor(7.5): {0}", etwa(a1, 7.5)) '7 
      Console.WriteLine("Round(7.5): {0}", etwa(a2, 7.5)) '8 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe bestätigt die korrekte Arbeitsweise:

Floor(7.5): 7 
Round(7.5): 8

Funktionsobjekt als Rückgabewert

Ein Funktionsobjekt stellt implizit einen Datentyp dar, der auch als Rückgabetyp einer Funktion dienen kann. Damit dieser Typ benannt werden kann, muss ein Delegate deklariert worden sein, das in den Parametertypen und dem Typ des Rückgabewerts mit der Funktionsobjektvariablen übereinstimmt. Das folgende Codefragment verwendet ein Delegate mit einer ganzen Zahl als Parameter und einer Fließkommazahl als Rückgabewert. Die Methode Anteil() gibt ein Funktionsobjekt mit passender Signatur zurück, das in Typ() der Variablen ant zugewiesen wird. Von nun an kann ant wie die Funktion AntFun benutzt werden.


'...\Klassendesign\Funktionen\Rueckgabewert.vb

Option Strict On 
Namespace Klassendesign 
  Module Rückgabewert

    Delegate Function AntFun(ByVal secs As Int32) As Double

    Function Anteil() As AntFun 
      Return Function(secs As Int32) Now.Second / secs 
    End Function

    Sub Typ() 
      Dim ant As AntFun = Anteil() 
      Console.WriteLine("Minutenanteil: {0}", ant(60)) 
      Console.WriteLine("Stundenanteil: {0}", ant(3600)) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Sichtbarkeit und Lebensdauer von Werten

Ein Funktionsobjekt hat Zugriff auf all die Variablen, die während seiner Definition erreichbar sind. Im folgenden Codefragment wird innerhalb des Funktionsobjekts fun sowohl auf eine Modulvariable a als auch auf eine lokale Variable b zugegriffen. Die Ausgabe ist der Zeile als Kommentar angehängt.


'...\Klassendesign\Funktionen\Sichtbarkeit.vb

Option Strict On 
Namespace Klassendesign 
  Module Sichtbarkeit 
    Dim a As Integer = 10 
    Sub Test() 
      Dim b As Integer = 100 
      Dim fun = Function() a + b 
      Console.WriteLine("a+b: {0}", fun()) '110 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Was passiert, wenn zwischen der Definition eines Funktionsobjekts und seiner Verwendung Variablen geändert werden, auf die es zugreift? Zwei Fälle des Zugriffs sind dabei zu unterscheiden:

  • Der Zugriff erfolgt noch im selben Bereich.
  • Der Zugriff erfolgt in einem neuen Bereich.

Der erste Fall liegt vor, wenn das Funktionsobjekt in der gleichen Funktion benutzt wird, in der es definiert wurde. Dann verhält sich das Funktionsobjekt wie jede andere Funktion und nimmt die gerade gültigen Werte. Im zweiten Fall wird der Wert genommen, der gültig war, als die Funktion beendet wurde, die das Funktionsobjekt definiert hat. Ob eine Variable mit Dim oder Static deklariert wird, hat auf diesen Mechanismus keinen Einfluss. Das folgende Codefragment zeigt beide Fälle:


'...\Klassendesign\Funktionen\Abschluss.vb

Option Strict On 
Namespace Klassendesign 
  Module Abschluss

    Delegate Function IntFun() As Integer

    Sub Lebensdauer() 
      Console.WriteLine(„Definition Funktion“) 
      Dim lesen = Funktion() 
      Console.WriteLine("Lesen: {0}", lesen()) '3 
      Console.WriteLine("Lesen: {0}", lesen()) '3 
      Console.WriteLine("Definition Funktion") 
      lesen = Funktion() 
      Console.WriteLine("Lesen: {0}", lesen()) '6 
      Console.ReadLine() 
    End Sub

    Function Funktion() As IntFun 
      Static loc As Integer 
      Dim var = Function() loc 
      loc = loc + 1 
      Console.WriteLine("Lesen Definition: {0}", var()) '1,4 
      loc = loc + 2 
      Return var 
    End Function 
  End Module 
End Namespace

Die Ausgabe von Lebensdauer() lautet:

Definition Funktion 
Lesen Definition: 1 
Lesen: 3 
Lesen: 3 
Definition Funktion 
Lesen Definition: 4 
Lesen: 6

In der Prozedur Lebensdauer() wird durch den Aufruf Funktion() das erste Funktionsobjekt definiert. In Funktion() startet die lokale Variable loc mit dem Wert 0. Danach wird sie um 1 erhöht und wird das erste Funktionsobjekt definiert. Es greift nun auf den aktuellen Wert 1 zu. Danach wird loc um 2 erhöht und die Funktion verlassen. In der Prozedur Lebensdauer() wird das Funktionsobjekt nun zweimal verwendet, jedes Mal greift es auf den Wert zu, den loc hatte als Funktion() verlassen wurde, hier 3. Der nächste Aufruf von Funktion() erhöht loc um 1 und definiert das zweite Funktionsobjekt. Das verwendet beim Aufruf innerhalb von Funktion() den gerade aktuellen loc-Wert 4. Danach wird loc um 2 erhöht und Funktion() verlassen. In der Prozedur Lebensdauer() greift das zweite geschaffene Funktionsobjekt auf den Wert von loc zu, den die Variable beim letzten Verlassen von Funktion() hatte, nämlich 6.


Hinweis
Das Zur-Verfügung-Stellen des richtigen (Variablen-)Kontexts für den Funktionsaufruf wird Funktionsabschluss (engl. Closure) genannt.



Rheinwerk Computing - Zum Seitenanfang

3.9.6 Umwandlungen Zur nächsten ÜberschriftZur vorigen Überschrift

Erwartet der Delegate-Typ eine Prozedur, kann als konkreter Wert der Delegate-Variablen auch eine Funktion verwendet werden. Der Rückgabewert wird dann schlicht ignoriert. Umgekehrt ist dies nicht möglich. Ein Delegate-Typ mit Rückgabewert kann auch nur mit einer Funktion mit Rückgabewert verwendet werden. Dabei muss der Wert der konkreten Funktion dem des Delegates entsprechen oder sich implizit in diesen umwandeln lassen.

Das Konzept greift auch bezüglich der Parameter. Jeder Typ eines Parameters der der Delegate-Variablen zugewiesenen konkreten Funktion oder Prozedur muss mit dem Typ des korrespondierenden Parameters im Delegate übereinstimmen oder sich in diesen implizit umwandeln lassen. Das folgende Codefragment zeigt, wie durch implizite Umwandlung ein Integer-Parameter anstatt eines Short-Parameters im Delegate verwendet werden kann. Analog wird der Integer-Rückgabewert implizit in den Double-Rückgabewert des Delegates umgewandelt.

Delegate Function Oper(ByVal v As Short, ByVal v As Short) As Double 
Dim op As Oper = Function(v1 As Integer, v2 As Short) v1 \ v2

Bei Funktionsobjekten gibt es eine »unglückliche« Stolperfalle. Wird ein Delegate mit Parametern vereinbart, akzeptiert eine Delegate-Variable dieses Typs ein Funktionsobjekt ohne Parameter. Funktionsobjekte mit mindestens einem Parameter und nicht passender Anzahl oder falschen Typen werden zurückgewiesen. Der Aufruf muss mit korrekter Parameterzahl und Parametertypen erfolgen. Da das Funktionsobjekt keine Parameter hat, werden die übergebenen Parameter schlicht ignoriert. Das folgende Codefragment zeigt eine solche verwirrende Situation:


'...\Klassendesign\Funktionen\DelegateParameter.vb

Option Strict On 
Namespace Klassendesign 
  Module DelegateParameter

    Delegate Function Oper(ByVal v As Short,ByVal v As Short) As Double

    Sub Test() 
      Dim d As Oper = Function() –1 
      Console.WriteLine("15/3 {0}", d(15, 3)) 
      d = Function(v1 As Double, v2 As Double) v1 / v2 
      Console.WriteLine("15/3 {0}", d(15, 3)) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe komplettiert die Verwirrung:

15/3 –1 
15/3 5

Rheinwerk Computing - Zum Seitenanfang

3.9.7 Grafikbibliothek: Vergleich von Rechtecken topZur vorigen Überschrift

In diesem Abschnitt erweitern wir die in Abschnitt 3.1.12, »Grafikbibliothek: Beispiel für Kapitel 3 und 4«, eingeführte und zuletzt in Abschnitt 3.8.3, »Grafikbibliothek: Position des Rechtecks«, erweiterte Grafikanwendung. Zuerst werden zwei Delegates (Funktionszeiger) hinzugefügt. Der erste dient zum Vergleichen von Rechtecken, der zweite zur Ermittlung einer Maßzahl zum Vergleich. Die allgemeine Methode Vgl() führt mittels der Funktion vom Typ Dimension den Vergleich aus und wird in VglFläche() und VglUmfang() mit passenden Funktionsobjekten aufgerufen. In der Testmethode Vergleichen() werden schließlich die übergebenen Rechtecke mit einigen Funktionen in einer For Each-Schleife verglichen.


'...\Klassendesign\Graphik\Delegate.vb

Option Explicit On 
Namespace Klassendesign 
  Partial Public Class Rechteck

    Delegate Sub Vergleich(ByVal rechteck As Rechteck) 
    Delegate Function Dimension(ByVal rechteck As Rechteck) As Double

    Sub Vgl(ByVal r As Rechteck, ByVal f As Dimension, ByVal art As String) 
      Console.WriteLine("{0}x{1} hat {2} {3} als {4}x{5}", r.a, r.b, _ 
                        If(f(r) >= f(Me), "mehr", "weniger"), art, a, b) 
    End Sub

    Sub VglFläche(ByVal recht As Rechteck) 
      Vgl(recht, Function(r As Rechteck) r.a * r.b, "Fläche") 
    End Sub

    Sub VglUmfang(ByVal recht As Rechteck) 
      Vgl(recht, Function(r As Rechteck) r.a + r.b, "Umfang") 
    End Sub

    Sub Vergleichen(ByVal ParamArray rechtecke() As Rechteck) 
      Dim vgl() As Vergleich = {AddressOf VglFläche, AddressOf VglUmfang} 
      For Each v As Vergleich In vgl 
        For Each r As Rechteck In rechtecke : v(r) 
      Next r, v 
    End Sub 
  End Class 
End Namespace

Zum Test werden einige Rechtecke erzeugt und mit einem weiteren Rechteck verglichen.


'...\Klassendesign\Zeichner\Delegate.vb

Option Explicit On 
Namespace Klassendesign 
  Partial Class Zeichner 
    Sub Delegates() 
      Dim rechtecke() As Rechteck = _ 
        {New Rechteck(6, 7), New Rechteck(12, 4), New Rechteck(7, 8)} 
      Dim rechteck As New Rechteck(7, 7) 
      rechteck.Vergleichen(rechtecke) 
    End Sub 
  End Class 
End Namespace

Zur Kontrolle sehen Sie hier die Ausgabe der Methode Delegates():

6x7 hat weniger Fläche als 7x7 
12x4 hat weniger Fläche als 7x7 
7x8 hat mehr Fläche als 7x7 
6x7 hat weniger Umfang als 7x7 
12x4 hat mehr Umfang als 7x7 
7x8 hat mehr Umfang als 7x7

Die nächste Erweiterung erfolgt in Abschnitt 3.10.6, »Grafikbibliothek: Größenänderungen von Rechtecken überwachen«.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual Basic 2008
Visual Basic 2008
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Visual Basic 2012






 Visual Basic 2012


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
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