3.4 Bindung
3.4.1 Klassenbindung mit Shared
Bisher haben wir uns mit Zuständen und Methoden beschäftigt, die in Zusammenhang mit einem Objekt standen. Es gibt Situationen, in denen ein Zustand für alle Objekte einer Klasse gleich ist. Ein Beispiel ist eine Variable, die speichert, wie viele Zustandsvariablen ein Objekt hat. Diese Anzahl ist im Quelltext festgelegt und ändert sich nicht, egal ob Sie kein oder tausend Objekte herstellen. Es ist dann nur logisch, diese Variable nicht extra in jedem Objekt zu speichern, sondern nur einmalig in der Klasse. Analog dazu gibt es Methoden, die gar keine Zustandsinformationen des Objekts benötigen. Zum Beispiel hat eine Methode, die das Quadrat einer Zahl berechnet, nichts mit dem Zustand eines Objektes zu tun. Auch hier ist es konsistenter, solch ein Verhalten an die Klasse und nicht an das Objekt zu binden. Diese Art der Bindung wird durch den Modifikator Shared festgelegt. Solche Elemente heißen auch statisch.
Schauen wir uns das Konzept anhand von zwei Beispielen an. Die folgende Anwendung zählt Rechtecke. Da alle Informationen als Parameter übergeben werden, ist es nicht sinnvoll, die Zählmethode an ein konkretes Objekt zu binden. Daher wird sie mit Shared gekennzeichnet.
'...\Klassendesign\Bindung\SharedMethode.vb |
Option Explicit On
Namespace Klassendesign
Class RectZahl
Public a, b As Double
Public Shared Function Anzahl(ByVal res() As RectZahl) As Integer
For Each re As RectZahl In res
If re.a * re.b > 30 Then Anzahl += 1
Next
End Function
End Class
...
End Namespace
Im zweiten Beispiel schauen wir uns klassengebundene Variablen an. Das nächste Codefragment zeigt eine Auto-Klasse mit einem Besitzer für jedes Auto und einer Autotypbezeichnung, die für alle Autos gleich ist (Shared). In Test() werden die Besitzer festgelegt und in zwei Druckbefehlen protokolliert. Der Druckbefehl zeigt, wie über den Klassennamen und nicht über eine Objektreferenz auf die Variable Name zugegriffen wird, die an die Klasse gebunden ist. Danach ändert der erste Autobesitzer den Autonamen in 2CV. Dies zeigt, dass es möglich ist, über eine Objektreferenz auf ein an die Klasse gebundenes Klassenmitglied zuzugreifen. Schließlich wird der andere Besitzer, der nichts geändert hat, erneut protokolliert.
'...\Klassendesign\Bindung\SharedFeld.vb |
Option Explicit On Namespace Klassendesign Class TPV 'Toute Petite Voiture Public Besitzer As String Public Shared Name As String = "Ente" End Class Module KlassenbindungFeld Sub Test() Dim f1 As TPV = New TPV() Dim f2 As TPV = New TPV() f1.Besitzer = "Helga Kohl" f2.Besitzer = "Ralf Schröder" Console.WriteLine(f1.Besitzer & " fährt " & TPV.Name) Console.WriteLine(f2.Besitzer & " fährt " & TPV.Name) f1.Name = "2CV" Console.WriteLine(f2.Besitzer & " fährt " & TPV.Name) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass die Änderung durch den ersten Besitzer auch die Autobezeichnung des zweiten Besitzers geändert hat. Alle Autos desselben Typs TPV haben das Feld Name gemeinsam. Es ist also egal, von welchem Objekt aus eine klassengebundene Variable geändert wird.
Helga Kohl fährt Ente Ralf Schröder fährt Ente Ralf Schröder fährt 2CV
Hinweis |
Um die Bindung einfacher erkennen zu können, ist es sehr ratsam, mit dem Klassennamen auf ein klassengebundenes Element zuzugreifen. |
Zugriff durch Objekt
Wenn auf ein klassengebundenes Element zugegriffen wird, verwendet der Compiler ausschließlich den Typ des qualifizierenden Bestandteils für den Zugriff. Eine Auswertung findet nicht statt. Im folgenden Codefragment wird über eine Funktion auf eine Klassenvariable zugegriffen. Jede Auswertung macht sich durch einen Ausdruck in der Konsole bemerkbar.
'...\Klassendesign\Bindung\Auswertung.vb |
Option Explicit On Namespace Klassendesign Class Mathe Public Shared Pi As Double = 3.1415926 End Class Module Auswertung Function Zugriff() As Mathe Console.WriteLine("Zugriff") Return New Mathe() End Function Sub Test() Console.WriteLine("Pi = {0}", Zugriff().Pi) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass die Funktion Zugriff() nicht ausgewertet wurde.
Pi = 3.1415926
Zugriff auf Objekt
Aus einer klassengebundenen Methode heraus kann nicht auf eine Objektvariable oder Objektmethode zugegriffen werden. Ein mit Shared an die Klasse gebundenes Element weiß nichts von Objekten. Das folgende Codefragment erzeugt daher einen Compilerfehler, denn: welches der mit New Statisch() erzeugten Objekte sollte Zugriff() ansprechen?
Class Statisch
Public wert As Double
Public Shared Sub Zugriff()
Console.WriteLine(wert) 'Fehler!!
End Sub
End Class
Module
Module sind eine besondere Form von Klassen. Sie enthalten ausschließlich statische Mitglieder (das heißt, alle sind an die Klasse gebunden). Daher ist es auch nicht sinnvoll, Objekte eines Moduls zu erzeugen, und dies ist daher verboten. Die Mitglieder eines Moduls werden behandelt, als stünden sie zusätzlich noch parallel zum Modul, sodass sie ohne die Qualifikation durch den Modulnamen angesprochen werden können (siehe Abschnitt 2.5.5, »Sichtbarkeit und Lebensdauer«). Schließlich können Module nicht als Datentyp auftreten, zum Beispiel in generischen Typen (siehe Abschnitt 4.4, »Generisches«). Als Nicht-Typ können sie auch nicht innerhalb der Vererbung eingesetzt werden. (Ausnahme: Sie selbst haben Zugriff auf statische Methoden der Klasse Object.)
Hinweis |
Ein Modul entspricht einer static-Klasse in C#. |
3.4.2 Klassenkonstruktoren
Eine Klasse hat eine Initialisierungsroutine, die aufgerufen wird, wenn die Klasse geladen wird, bevor das erste Objekt erzeugt wird. Der sogenannte Klassenkonstruktor wird meist verwendet, um klassengebundene Zustände zu initialisieren. Ein Klassenkonstruktor kann nicht einen anderen Klassenkonstruktor aufrufen, und es besteht kein Spielraum in der Syntax (keine Parameter, keine Modifikatoren). Die Anweisungen sind beliebig, also auch optional.
Shared Sub New() [<Anweisungen>] End Sub |
Hinweis |
In Modulen fällt das Shared weg. |
Klassen werden in der Reihenfolge geladen, in der sie zum ersten Mal gebraucht werden. Im nächsten Codefragment verwendet Modul den Typ Klasse. Da in einem Modul alle Mitglieder implizit klassengebunden sind, fehlt die Angabe von Shared. Die Klassenkonstruktoren beider Datentypen machen sich durch eine Konsolenausgabe bemerkbar.
'...\Klassendesign\Konstruktoren\Klasse.vb |
Option Explicit On Namespace Klassendesign Class Klasse Shared Sub New() Console.WriteLine("Klasse") End Sub End Class Module Modul Friend klasse As Klasse = New Klasse() Sub New() Console.WriteLine("Modul") End Sub End Module Module Klassenkonstruktor Sub Test() Equals(Modul.klasse, Nothing) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt die Ladereihenfolge der Datentypen:
Klasse Modul
Hinweis |
Ein nicht gebrauchter Datentyp wird auch nicht initialisiert. Es reicht dazu nicht, nur den Typ zu verwenden, wie zum Beispiel in GetType. |
3.4.3 Externe Funktionen
Sollten Sie .NET als zu enges Korsett empfinden, können Sie Funktionen in externen Bibliotheken aufrufen. Das könnte der Fall sein, wenn Sie statt eines Fenstertitels eine Grafik in der Titelleiste sehen möchten, oder wenn Sie Programme für Mobilgeräte schreiben und die Version des .NET Compact Frameworks nicht alle benötigten Funktionen für das Gerät zur Verfügung stellt. Damit eine externe Funktion genutzt werden kann, muss ihre Signatur deklariert werden. Dazu stehen zwei gleichwertige Syntaxvarianten zur Verfügung (Sub erfolgt analog, optionale Teile stehen in eckigen Klammern, kursive Teile müssen Sie Ihren Bedürfnissen anpassen, und wie üblich trennt der Unterstrich Teile einer logischen Zeile):
[<Sichtbarkeit>] Declare [<Zeichensatz>] Function name Lib "datei" _ [Alias "nameInBibliothek"] ([<Parameter>]) As typ [<Sichtbarkeit>] <System.Runtime.InteropServices.DllImport("datei")> _ Public Shared Function name([<Parameter>]) As typ |
Hinweis |
Externe Methoden sind implizit an die Klasse gebunden (Shared). |
Die Variante mit spitzen Klammern nutzt das Attribut DllImport, das sich über Parameter anpassen lässt. Attribute werden in Abschnitt 4.8, »Attribute«, erläutert. Die möglichen Sichtbarkeiten sind in Abschnitt 3.2.3, »Sichtbarkeitsmodifikatoren«, beschrieben. Die Spezifikation des Zeichensatzes betrifft die Konvertierung (auch Marshalling genannt) zwischen Bibliotheksfunktion und Visual Basic sowie die Wahl des Funktionsnamens innerhalb der Bibliothek (siehe Tabelle 3.5).
Art | Marshalling | Funktionsname Fun |
Ansi (Standard) |
ANSI |
Unmodifiziert |
Unicode |
Unicode |
Unmodifiziert |
Auto |
nach Funktionsname |
ANSI-Plattform (<=ME): erst Fun, wenn nicht gefunden, dann FunA Unicode-Plattform (>=NT): erst FunW, wenn nicht gefunden, dann Fun |
Hinweis |
Zeichenketten in externen Bibliotheken können – im Gegensatz zu Visual Basic – veränderlich sein. |
Das folgende Codefragment zeigt die externe Funktion GetConsoleTitle. Auf der Internetseite http://www.pinvoke.net/default.aspx/kernel32.GetConsoleTitle habe ich mir die Anregung zu diesem Beispiel geholt. Durch die Zeichensatzspezifikation Auto wird der richtige Funktionsname gewählt. Eine Alternative dazu ist die Alias-Klausel in den drei Zeilen dar-über. Die Verwendung eines Attributs zeigen die vier ersten auskommentierten Zeilen.
'...\Klassendesign\Bindung\Extern.vb |
Option Explicit On Imports SB = System.Text.StringBuilder Namespace Klassendesign Class Extern '<System.Runtime.InteropServices.DllImport("kernel32")> _ ' Shared Function GetConsoleTitle( _ ' ByVal title As SB, ByVal buff As UInt32) As UInt32 'End Function 'Declare Function GetConsoleTitle Lib "kernel32.dll" _ ' Alias “GetConsoleTitleA” ( _ ' ByVal title As SB, ByVal buff As UInt32) As UInt32 Declare Auto Function GetConsoleTitle Lib "kernel32" ( _ ByVal title As SB, ByVal buffSize As UInt32) As UInt32 Shared Sub Test() Dim sb As SB = New SB(1001) Dim size As UInt32 = CType(sb.Capacity, UInt32) Dim len As UInt32 = GetConsoleTitle(sb, size) Console.WriteLine(sb.ToString()) Console.WriteLine(len & " Zeichen") Console.ReadLine() End Sub End Class End Namespace
Die Ausgabe zeigt, dass das Nullzeichen der Win32-Funktion, das das Ende einer Zeichenkette signalisiert, nicht mit kopiert wurde.
file:///E:/VisualStudioWS/Klassendesign/Bindung/bin/Debug/Bindung.EXE 69 Zeichen
Hinweis |
Externe Methoden können weder Ereignisse behandeln noch Schnittstellen implementieren. Auch dürfen sie keine Typparameter enthalten (siehe Abschnitt 4.4, »Generisches«). |
Fehlermeldungen
Oft ist es schwierig, das Scheitern einer externen Funktion zu analysieren. Für Funktionen aus der Windows-API gibt es Möglichkeiten, Fehlertexte zu erhalten. Das folgende Code- fragment deklariert die externe Funktion GetLastError(), da bei meinen Tests der Aufruf Marshal.GetLastWin32Error() immer 0 zurückgab. In der Methode LastError() wird aus der Fehlernummer ein Fehlertext ermittelt. Schließlich wird die externe Funktion SetCurrentDirectory() als Testfunktion deklariert und in der Methode Test() ein »falscher« Aufruf erzeugt.
'...\Klassendesign\Bindung\ExternFehler.vb |
Option Explicit On Imports System.Runtime.InteropServices Namespace Klassendesign Class ExternFehler <DllImport("kernel32.dll", CharSet:=CharSet.Unicode)> _ Private Shared Function GetLastError() As Integer End Function Public Shared Function LastError() As String 'Dim errno As Integer = Marshal.GetLastWin32Error() Dim errno As Integer = GetLastError() Dim mess As String = New ComponentModel.Win32Exception(errno).Message Return mess End Function <DllImport("kernel32")> _ Shared Function SetCurrentDirectory(ByVal dir As String) As Boolean End Function Shared Sub Test() SetCurrentDirectory("?") Console.WriteLine(LastError()) Console.ReadLine() End Sub End Class End Namespace
Die Ausgabe gibt zumindest einen Hinweis:
The filename, directory name, or volume label syntax is incorrect
3.4.4 Grafikbibliothek: Rechteckübergreifendes
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.3.7, »Grafikbibliothek: Zugriffsmethoden auf die Größe des Rechtecks«, erweiterte Grafikanwendung. Zur späteren Protokollierung aller erzeugten Rechtecke führen wir je eine klassengebundene Methode und einen Zähler ein, der im Klassenkonstruktor initialisiert wird. Außerdem kommt eine Methode zur Addition von Rechteckflächen hinzu.
'...\Klassendesign\Graphik\Klassenbindung.vb |
Option Explicit On Namespace Klassendesign Partial Public Class Rechteck Private Shared anzahl As Integer Shared Sub New() anzahl = 0 End Sub Shared Sub Erzeugt() Console.WriteLine("Anzahl der erzeugten Rechtecke: {0}", anzahl) End Sub Shared Sub Gesamtfläche(ByVal ParamArray rechtecke() As Rechteck) Dim fläche As Double For Each r As Rechteck In rechtecke : fläche += r.a * r.b : Next Console.WriteLine("Gesamtfläche: {0}", fläche) End Sub End Class End Namespace
Zum Testen definieren wir ein paar Rechtecke, deren Gesamtfläche wir ermitteln. Bitte beachten Sie die Verwendung des Klassennamens statt eines Objekts. Die Funktionalität, die mit der Anzahl zusammenhängt, testen wir in der nächsten Erweiterung in Abschnitt 3.5.3, »Grafikbibliothek: Initialisierung des Rechtecks«.
'...\Klassendesign\Zeichner\Klassenbindung.vb |
Option Explicit On
Namespace Klassendesign
Partial Class Zeichner
Sub Klassenbindung()
Dim r1 As Rechteck = New Rechteck() : r1.Größe(3, 4)
Dim r2 As Rechteck = New Rechteck() : r2.Größe(11, 2)
Dim r3 As Rechteck = New Rechteck() : r3.Größe(7, 7)
Rechteck.Gesamtfläche(r1, r2, r3)
End Sub
End Class
End Namespace
Zur Kontrolle sehen Sie hier die Ausgabe der Methode Klassenbindung():
Gesamtfläche: 83
Die nächste Erweiterung erfolgt in Abschnitt 3.5.3, »Grafikbibliothek: Initialisierung des Rechtecks«.
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.