Implementierung von Operatoren

Nachdem diese Vorbereitungen abgeschlossen sind, schreiten wir zur nächsten Tat: dem eigentlichen Implementieren der statischen Operatorenprozeduren. Dazu können wir dann die aus der Mathematik bekannten Rechenoperatoren verwenden.

Hierbei unterscheiden wir zwischen zwei Typen: den eigentlichen Operatoren, wie +, –, *, / etc. und den Operatoren, die zur Konvertierung von Typen dienen. Wir beginnen mit der ersten Gruppe – der Implementierung der von uns benutzten Rechenoperatoren. Die generelle Ausführung der Operatorenimplementierung lautet folgendermaßen:

Public [Class|Structure] OpTyp
    Public Shared Operator OpChar(ByVal objVar1 As [OpTyp|Typ1], ByVal objVar2 As [OpTyp|Typ2]) As Typ3
        ' Hier steht der Code, der die eigentliche Operation durchführt
    End Operator
End [Class|Structure]

Dieser Rumpf soll verdeutlichen, auf was es ankommt:

  • Operatoren lassen sich auf Klassen und Strukturen anwenden.
  • Welcher Operator (+, ­–, *, / etc.) zur Anwendung kommt, wird durch OpChar bestimmt. Untenstehende Tabelle listet auf, welche Rechenoperatoren sich implementieren lassen (und wofür sie eigentlich gedacht sind).
  • Mindestens einer der Parameter, den Sie einer Operatorenprozedur übergeben, muss vom Typ sein wie die Klasse bzw. Struktur, die die Operatorenprozedur definiert.
  • Die Operatorenprozedur muss, wie schon erwähnt, statischer Natur und deswegen mit dem Modifizierer Shared definiert sein.
  • Der Rückgabetyp kann beliebig sein. Auf unser Beispiel angewendet, würde die Additionsroutine sich dann folgendermaßen gestalten:
Public Shared Operator +(ByVal sstring1 As SuperString, ByVal sstring2 As SuperString) As SuperString
    Return sstring1.Addieren(sstring2)
End Operator

Durch den Plus-Operator sollen die beiden Parameter, die jeweils links und rechts vom Plus-Operator stehen, »addiert«, also in unserem Fall miteinander verkettet werden. Der links vom Operator stehende Parameter entspricht dabei dem ersten der Operatorenprozedur übergebenen Parameter, der rechts vom Operator stehende dem zweiten Parameter.

Hinweis: Diese Art der Implementierung funktioniert problemlos, da wir unseren SuperString-Typ auf Basis einer Struktur, also eines Wertetyps, konzipiert haben. Hätten wir ihn als Referenztyp konzipiert, wären bei der Implementierung der Operatorenprozeduren auf diese Weise Probleme buchstäblich vorprogrammiert. Lesen Sie vor der Implementierung von Operatorenprozeduren in eigene Klassen deswegen auch unbedingt den Abschnitt »Fehler: Referenz nicht gefunden« ab Seite Fehler: Referenz nicht gefunden.

Sobald diese Operatorenprozedur Bestandteil der Klasse geworden ist, können wir den Code im Modul für die Addition der beiden Strings umstellen. Aus

locSuperString = locSuperString.Addieren( _
        New SuperString(vbNewLine + " - Toll, was?"))

wird dann

locSuperString = locSuperString + New SuperString(vbNewLine + " - Toll, was?")

Und das ist schon wesentlich bequemer zu handhaben und besser lesbar, finden Sie nicht? In diesem Stil haben wir dann die Möglichkeit, auch die anderen Operatoren zu implementieren:

Public Shared Operator -(ByVal sstring1 As SuperString, ByVal sstring2 As SuperString) As SuperString
        Return sstring1.Subtrahieren(sstring2)
    End Operator

    Public Shared Operator *(ByVal sstring1 As SuperString, ByVal anzahl As Integer) As SuperString
        Return sstring1.Vervielfachen(anzahl)
    End Operator

    Public Shared Operator /(ByVal sstring1 As SuperString, ByVal trennzeichen As Char) As SuperString()
        Return sstring1.Teilen(trennzeichen)
    End Operator

Überladen von Operatorenprozeduren

Nun gibt es noch einen Punkt, der bei Operatorenprozeduren noch angepasst werden sollte: Unsere ursprüngliche Subtraktionsroutine gibt es in zwei Überladungsversionen. Die erste übernimmt einen SuperString als Parameter; dieser wird dann in der Ausgangszeichenkette gesucht und aus ihr entfernt, wenn es eine Suchübereinstimmung gab. Die zweite Möglichkeit: Sie können einen Integer-Wert als Parameter angeben, der die Anzahl der Zeichen bestimmt, die vom hinteren Teil der Zeichenkette entfernt werden sollen. Diese Funktion ist im Moment noch nicht durch einen Operator aufrufbar.

Doch wie »normale« Methoden können Sie auch für Operatorenprozeduren Überladungen anwenden. Es gilt dabei das in Kapitel 10 im Abschnitt »Überladen von Methoden, Konstruktoren und Eigenschaften« Gesagte. Wenn wir die Sammlung der Operatorenprozeduren um die folgende überladene Methode ergänzen

Public Shared Operator -(ByVal sstring1 As SuperString, ByVal anzahl As Integer) As SuperString
        Return sstring1.Subtrahieren(anzahl)
End Operator

wird es möglich, beide Versionen der Subtraktion im Modul auf Operatoren umzustellen:

.
.
.
        'Subtrahieren (rauslöschen) anderer Strings
        Console.WriteLine()
        Console.WriteLine("'Subtrahieren' von Strings - ', was?' abziehen:")
        'locSuperString = locSuperString.Subtrahieren( _
        '        New SuperString(", was"))
        locSuperString = locSuperString - New SuperString(", was")
        Console.WriteLine(locSuperString.ToString)
        Console.WriteLine()

        'Subtrahieren ist überladen - geht auch mit der Anzahl
        'der letzten Zeichen, die entfernt werden sollen.
        Console.WriteLine("'Subtrahieren' von Strings - die letzten 9 Zeichen abziehen:")
        locSuperString = locSuperString - 9
        Console.WriteLine(locSuperString.ToString)
        Console.WriteLine()
.
.
.

Implementierung von Vergleichsoperatoren

Prinzipiell lassen sich Vergleichsoperatoren für benutzerdefinierte Typen ähnlich implementieren wie Rechenoperatoren. Es gibt nur zwei zusätzliche Bedingungen:

  • Sie müssen als Funktionsergebnis grundsätzlich einen booleschen Datentyp zurückliefern, der bestimmt, ob der Vergleich erfolgreich war oder nicht.
  • Sie müssen Vergleichsoperatoren paarweise implementieren. Wenn Sie den Operator implementieren, der auf Gleichheit prüft, müssen Sie auch den implementieren, der auf Ungleichheit prüft. Implementieren Sie den Vergleich auf größer, müssen Sie den auf kleiner ebenfalls einbauen. Das Gleiche gilt für größer gleich und kleiner gleich

Die Implementierung von Vergleichsoperatoren für unsere SuperString-Klasse sieht folgendermaßen aus:

Public Shared Operator <>(ByVal sString1 As SuperString, ByVal sString2 As SuperString) As Boolean
        Return (sString1.ToString <> sString2.ToString)
    End Operator

    Public Shared Operator =(ByVal sString1 As SuperString, ByVal sString2 As SuperString) As Boolean
        Return (sString1.ToString = sString2.ToString)
    End Operator

    Public Shared Operator <(ByVal sString1 As SuperString, ByVal sString2 As SuperString) As Boolean
        Return (sString1.ToString < sString2.ToString)
    End Operator

    Public Shared Operator >(ByVal sString1 As SuperString, ByVal sString2 As SuperString) As Boolean
        Return (sString1.ToString > sString2.ToString)
    End Operator

Implementierung von Typkonvertierungs­operatoren mit dem Operator CType

Typkonvertierungsoperatoren sind ein zweischneidiges Schwert. Sie erhöhen den »Degree of Convenience« auf der einen Seite um ein weiteres Maß, können aber unter Umständen auch zu erheblichen Problemen führen – aber dazu später mehr.

Erinnern wir uns: Eine implizite Konvertierung, also eine, bei der nichts Zusätzliches gemacht werden muss, können Sie anwenden, wenn Sie einen kleineren Datentyp in einen größeren Datentyp überführen – beispielsweise einen Integer-Wert in einen Long-Wert. Dazu ein Beispiel:

'Hier geht's mit impliziter (da typerweiternder) Konvertierung
Dim einLong As Long, einInteger As Integer
einInteger = 10
einLong = einInteger

Vor den umgekehrten Weg einer typverkleinernden Typkonvertierung schiebt der Basic-Compiler jedoch erst einmal einen Riegel – jedenfalls so lange, bis Sie sich mit CType (oder einem Cxxx-Operator) da »rauskaufen«. Sie sollten sich bewusst sein, dass Daten bei einer typverkleinernden (oder den Typ völlig verändernden) Konvertierung verloren gehen können, und deswegen macht Sie Visual Basic mit dem Einsatzzwang von CType darauf aufmerksam. Derart explizite Konvertierungen sehen dann so

'Das hier erfordert eine explizite, da typverkleinernde Konvertierung
einLong = 1000
einInteger = CType(einLong, Integer)

oder so aus:

'Aber auch diese Konvertierung muss explizit durchgeführt werden
Dim einDouble As Double, einString As String
einString = "1.828.488.382,45"
einDouble = CType(einString, Long)

Nun werden Typen natürlich nicht »einfach so« konvertiert – gerade bei einer komplexeren Typkonvertierung wie von einer Zeichenkette in einen numerischen Wert läuft ein gar nicht so anspruch­loses Programm ab, das diese Konvertierung vornimmt.

Und ein solches Programm – oder besser: eine solche Unterroutine – können Sie mit den so genannten Operator CType-Prozeduren für Ihre eigenen Datentypen implementieren. Dabei haben Sie es in der Hand, ob eine implizite oder eine explizite Konvertierung erfolgen soll. Für unser Beispiel wäre es doch schön, wenn die folgende Zeile funktionieren würde:

Dim einSuperString As SuperString = "Dies ist eine Zeichenkette"

Das können Sie haben. Bei der Konstanten, die dem SuperString zugewiesen wird, handelt es sich um eine vom Typ String. Da wir an dieser Stelle ohne CType arbeiten wollen (und können, denn es droht bei der Konvertierung kein Verlust), müssen wir eine Konvertierungsoperatorenprozedur implementieren, die den Datentyp erweitert. Und das geht so (und jetzt halten Sie sich fest, was die Modifizierer der folgenden Prozedur betrifft):

Public Shared Widening Operator CType(ByVal normaloString As String) As SuperString
    Return New SuperString(normaloString)
End Operato

Operator CType zeigt hier an, dass die Routine für eine Typkonvertierung zuständig ist. Der Modifizierer Widening bestimmt, dass es sich um eine implizite Konvertierung handelt, bei der die Ausformulierung von CType nicht notwendig ist. Und Shared schließlich, aber das wissen Sie ja, bestimmt, dass die Routine statischer Natur ist. Der eigentliche Konvertierungscode ist simpel: Die Prozedur legt auf Basis des übergebenden String eine neue SuperString-Instanz an und liefert diese als Funktionsergebnis zurück. Würden Sie wollen, dass der Entwickler, der Ihre Klasse verwendet, eine explizite Typkonvertierung mit CType einleiten muss, dann würden Sie den Modifizierer Widening durch Narrowing (verkleinern) ersetzen.

Natürlich ist diese »Erweitern-/Verkleinern-Geschichte« bei Datentypen nur eine Richtlinie. Ihnen steht es selbstverständlich frei, jeden Datentyp implizit oder explizit konvertierbar zu machen, ganz gleich, ob dabei Daten auf der Strecke bleiben können (wie bei Long zu Integer) oder nicht. Mir persönlich stoßen die Modifizierernamen ein wenig sauer auf, da sie mehr verwirren als nützen. Implicit und Explicit als Modifizierernamen wären mir lieber gewesen – aber wahrscheinlich hätte das wieder zu Problemen geführt, weil Explicit im Visual Basic-Dialekt schon seit Jahren im Rahmen von Option Explicit eingesetzt wird. Doch das ist eine bloße Vermutung. Übrigens: Die Konvertierung, die Sie nun implementiert haben, funktioniert derzeit nur in eine Richtung. Würden Sie versuchen, den umgekehrten Weg mit

Dim einString as String = einSuperString

zu gehen, sähen Sie in der Fehlerliste die Meldung:

Der Wert vom Typ "SuperStringVorstellung.SuperString" kann nicht in "String" konvertiert werden.

Damit beide Richtungen funktionierten, müssten Sie eine weitere CType Operator-Prozedur zum Projekt hinzufügen, nämlich:

Public Shared Widening Operator CType(ByVal SuperString As SuperString) As String
    Return SuperString.ToString
End Operator

Implementieren von Wahr- und Falsch-Auswertungsoperatoren

Eine Möglichkeit, Wahr- und Falsch-Auswertungsmechanismen zu implementieren, bestünde darin, implizite oder explizite Konvertierungen in den Datentyp Boolean für Ihre Klasse anzubieten. Doch Visual Basic sieht für diesen Zweck eine weitere Möglichkeit vor – die Operatoren IsTrue und IsFalse. Diese Operatoren stellen keine Anwendungsmöglichkeit im herkömmlichen Sinne bereit – Sie können die Operatoren IsTrue und IsFalse also nicht als Namen wie CType in Ihren eigenen Programmen einsetzen.

Sie dienen vielmehr nur als Hilfen bei der Definition von Operatorenprozeduren, um eine bestimmte Prozedur für eine Operation festzulegen, die ähnlich der impliziten Datentypkonvertierung, eigentlich gar keine Operatoren benötigt.

Unsere SuperString-Klasse könnte beispielsweise einen Mechanismus bereitstellen, der definiert, dass bestimmte Zeichenketteninhalte bei Auswertungen True ergeben, und alle anderen dagegen False zum Ergebnis haben. In diesem Fall ließe sich folgende Vorgehensweise implementieren:

Sub ExperimenteFürBoolscheAusdrücke()
        'Hier geht's mit impliziter (da typerweiternder) Konvertierung
        Console.Write("Möchten Sie weitere Daten eingeben (Ja, Nein):")
        Dim locSupStr As SuperString = Console.ReadLine
        If locSupStr Then
            Console.WriteLine("OK, dann geben Sie mal ein!")
        Else
            Console.WriteLine("dann halt nicht...")
        End If
        Console.WriteLine()
        Console.WriteLine("Taste drücken zum Beenden!")
    End Sub

Die entsprechenden Operatorenprozeduren, die diese Vorgehensweise ermöglichen, könnte man beispielsweise folgendermaßen aufbauen:

Public Shared Operator IsTrue(ByVal sString As SuperString) As Boolean
        Dim locString As String = sString
        locString = locString.ToUpper
        Select Case locString
            Case "JA"
                Return True
            Case "J"
                Return True
            Case "RICHTIG"
                Return True
            Case "WAHR"
                Return True
            Case "AUSGEWÄHLT"
                Return True
            Case "GEDRÜCKT"
                Return True
            Case "BESTÄTIGT"
                Return True
            Case "Y"
                Return True
            Case "YES"
                Return True
            Case "TRUE"
                Return True
            Case "CORRECT"
                Return True
            Case "SELECTED"
                Return True
            Case "PRESSED"
                Return True
            Case "ACCEPTED"
                Return True
            Case "CONFIRMED"
                Return True
        End Select
        Return False
    End Operator

    Public Shared Operator IsFalse(ByVal sString As SuperString) As Boolean
        If sString Then
            Return False
        Else
            Return True
        End If
    End Operator

Hinweis Wie bei Vergleichsoperatoren müssen Sie die Operatoren IsTrue und IsFalse paarweise einbinden. Auch wichtig: Sie sollten es bei der Einbindung von IsTrue und IsFalse in Erwägung ziehen, auch den Not-Operator zu verdrahten. Anderenfalls kann der Entwickler, der Ihre Klasse anwendet, einen Ausdruck wie den folgenden nicht anwenden:

If Not locSupStr Then
    .
    .
    .
End If

Übersicht der implementierbaren Operatoren

Sie werden staunen, welche Operatoren sich mit Operatorenprozeduren implementieren lassen. Die folgende Tabelle gibt Ihnen die Übersicht.

Wichtig: Beachten Sie, dass Sie einige Funktionen nur paarweise implementieren können.

Die Spalte Vorgesehene math. Funktion soll Ihnen übrigens nur eine grobe Themenrichtung für eine Implementierung vorgeben. Aber letzten Endes könnte Sie natürlich keiner daran hindern, dass Ihre Datentypen mit – addiert und mit + subtrahiert werden, wenn Sie es so wollen.

 

PotenzierenNach rechts verschieben

OperatorVorgesehene math. FunktionBemerkung
+ Addition
Subtraktion
\ Division ohne Rest
/ Division
^
& Verknüpfung
<< Nach links verschieben Normalerweise bitweise bei Integerzahlen
>> Normalerweise bitweise bei Integerzahlen
= Test auf Gleichheit Hiermit steuern Sie nicht die Zuweisung an eine Objektvariable – dies ist nur mit dem CType-Operator möglich. Wenn Sie diesen Operator implementieren, müssen Sie < > (ungleich) ebenfalls implementieren.
< > Test auf Ungleichheit Wenn Sie diesen Operator implementieren, müssen Sie = (gleich) ebenfalls implementieren
< Test auf kleiner Wenn Sie diesen Operator implementieren, müssen Sie > (größer) ebenfalls implementieren
> Test auf größer Wenn Sie diesen Operator implementieren, müssen Sie < (kleiner) ebenfalls implementieren
<= Test auf kleiner oder gleich Wenn Sie diesen Operator implementieren, müssen Sie > = (größer gleich) ebenfalls implementieren
>= Test auf größer oder gleich Wenn Sie diesen Operator implementieren, müssen Sie < = (kleiner gleich) ebenfalls implementieren
Like Test auf Ähnlichkeit
Mod Restwertermittlung
And Logische Und-Verknüpfung
Or Logische Oder-Verknüpfung
Xor Logische Exklusiv-Oder-Verknüpfung
Not Logische Negation
CType Steuerung der impliziten oder expliziten Typkonvertierung Verwenden Sie Narrowing für die explizite und Widening für die implizite Typkonvertierung
IsTrue Auswerten von booleschen Ausdrücken Sie müssen IsTrue und IsFalse paarweise implementieren. Sie sollten in diesem Fall auch einen Not-Operator zur Verfügung stellen. WICHTIG: Diese Operatoren können nur in Auswertungskonstruktionen wie If … Then … Else, Do While etc. verwendet werden – sie stellen keine implizite Konvertierung in den Datentyp Boolean bereit!
IsFalse Auswerten von booleschen Ausdrücken Es gilt das zu IsTrue gesagte. Mehr Informationen zu diesem Thema finden Sie im Abschnitt »Fehler: Referenz nicht gefunden« ab Seite Fehler: Referenz nicht gefunden

Mehr zu diesem Thema in: Visual Basic 2010 das Entwicklerbuch von Klaus Löffelmann, erhältlich bei Amazon auch auf Englisch, und bei Microsoft Press. Möchten Sie Klaus Löffelmann oder das ActiveDevelop-Team für professionelle Software-Entwicklungsprojekte (Migration, C#, Visual Basic.NET) buchen, informieren Sie sich auf www.activedevelop.de

klauslo am 04. Februar 2012

Tags: | Categories: Operatorenprozeduren