Visual Basic .NET: Typumwandlungen
Nicht immer ist man mit dem zufrieden, was man hat. Man hat einen Integer, braucht die Zahl aber als String, um sie in eine Datei schreiben zu können. Man hat von einer DLL-Funktion einen Boolean zurückbekommen, der angibt, ob alles glatt gelaufen ist, man will statt False und True jedoch 0 und 1 in eine Byte-Variable speichern. Manchmal ist man aber doch zufrieden mit dem, was man hat. Mit Visual Basic .NET können Sie demnach zufrieden sein, denn mit Visual Basic .NET haben Sie Typumwandlungen, explizite und implizite.
Typumwandlungen wandeln einen Typ in den anderen um. Das heißt, dass sie mit Typumwandlungen Ihren Integer in einen String und Ihren Boolean in ein Byte umwandeln können. Doch schon mit der Begrifflichkeit fangen die Probleme an: Die Typumwandlung wird auch „Konvertierung“ (lat. convertere = umwenden, verwandeln) genannt, entsprechend spricht man vom „Umwandeln“ und „Konvertieren“. Aus der C- und C++-Gemeinde kommt noch der Begriff „Cast“ für Umwandlung. Das Verb „casten“ wird nicht nur wie „umwandeln“ mit der Präposition „in“ (Umwandlung von Integer in String), sondern auch mit „auf“ verwendet (Casten von Integer auf String). Auch „nach“ kommt vor (Konvertierung von Integer nach String).
Man unterscheidet zwischen impliziter und expliziter Typumwandlung. Implizite Typumwandlung ist, wenn man selber nicht dran denkt und der Compiler die Typumwandlung einfügen muss. Explizite Typumwandlung ist, wenn der Computer nicht weiß, was er umwandeln soll oder dass er überhaupt umwandeln soll. Offensichtlicherweise ist implizite Umwandlung einfacher, da man da nichts tun muss, so wie in dem folgenden Beispiel.
Die MessageBox.Show-Funktion akzeptiert als ersten Parameter nur String-Variablen. Da 5 aber ein Integer-Literal ist, tritt die implizite Typumwandlung in Kraft. 5 wird (zur Laufzeit) von Integer (5) in String ("5") umgewandelt. Dieser Mechanismus klappt relativ gut, so dass Sie nur dann eingreifen sollten, wenn die automatische (implizite) Typumwandlung nicht funktioniert oder wenn sich ein Problem durch explizite Typumwandlung eleganter lösen lässt.
Die C-Funktionen
BearbeitenSollten Sie sich zu expliziter Typumwandlung entschließen, werden Sie schnell Bekanntschaft mit den C-Funktionen machen. Das sind eine Reihe von Funktionen, die in Visual Basic .NET fest definiert sind, weshalb ihre Namen auch Schlüsselwörter sind. Ich nenne sie hier zusammenfassend „C-Funktionen“, da alle mit C beginnen (für engl. convert = umwandeln). Die Typumwandlungsfunktionen verwenden Sie wie normale Funktionen. Alle C-Funktionen verlangen einen Parameter vom Typ Object. Object (eigentlich System.Object) kann alles sein, wirklich alles. Wie das möglich ist, sehen wir später, das hat mit objektorientierter Programmierung (genauer Vererbung und Polymorphie) zu tun. Alle C-Funktionen haben dann einen spezifischen Rückgabetyp, nämlich den Typ, in den umgewandelt wird. Der Name der konkreten C-Funktion orientiert sich an diesem Zieltyp. Zum Beispiel konvertiert CInt in Integer und CDbl in Double. Visual-Basic-6-Programmierern werden viele Namen bekannt vorkommen. (Allerdings haben sich auch einige Namen geändert; CBln heißt jetzt etwa CBool.) Diese Liste zeigt alle C-Funktionen:
|
|
|
Die C-Funktionen haben allerdings auch einige Einschränkungen, was den Ausgangsdatentyp angeht (aus logischen Gründen): Zum Beispiel akzeptiert CChar nur Char- und String-Werte. Achtung auch bei CStr: Die Konvertierung von Date-Werten erfolgt nach dem aktuellen Gebietsschema; es wird also das Datum in der landesüblichen Schreibweise ausgegeben.
Spezifische Funktionen
BearbeitenSystem.Convert
BearbeitenVöllig äquivalent zu den C-Funktionen verhalten sich die Funktionen in System.Convert. Der Name der Funktion setzt sich zusammen aus „To“ und dem Datentyp, zum Beispiel konvertiert die System.Convert.ToInteger-Funktion in Integer, die System.Convert.ToBoolean-Funktion in Boolean. Es wird allerdings empfohlen, nicht die Convert-Funktionen, sondern die C-Funktionen zu verwenden, da diese besser auf Visual Basic abgestimmt sind als die Convert-Funktionen und die folgenden Funktionen aus dem programmiersprachenunabhängigen .NET-Framework.
Type.Parse
BearbeitenDie Parse-Funktionen sind dem jeweiligen Datentyp zugeordnet; die Parse-Funktion mit dem Zieltyp Integer finden Sie unter dem Bezeichner Integer.Parse. Auch hier ist der erste Parameter der umzuwandelnde Ausdruck und der Rückgabewert der umgewandelte Wert. Als zweiten oder dritten Parameter können Sie weitere Informationen übergeben, in welchem Format der Ursprungsausdruck vorliegt. So können zum Beispiel hexadezimale Zahlen oder 1000er-Trennpunkte berücksichtigt werden. Falls Sie sich mit dieser Funktionalität, deren Komplexität den Rahmen sprengen würde, näher befassen wollen, empfehle ich Ihnen die ausführlichen Artikel in der MSDN Library (die Sie als SharpDevelop-Nutzer im Internet finden, siehe Link im Inhaltsverzeichnis). Suchen Sie einfach nach dem Namen der Funktion, zum Beispiel SByte.Parse. Beachten Sie, dass es keine String.Parse-Funktion gibt. Das Äquivalent ist nämlich String.Format.
String.Format
BearbeitenFormat-Funktionen sind wohl eine der flexibelsten Varianten der Datendarstellung. Da sie v.a. in der C-Familie sehr erfolgreich sind, wurden Sie ins .NET-Framework übernommen. Somit lässt sich diese Funktionalität auch in Visual Basic .NET nutzen. Da die Format-Funktion zum Datentyp String gehört, finden Sie Sie unter dem Bezeichner String.Format. Das Erstaunliche an der String.Format-Funktion ist, dass sie beliebig viele Parameter haben kann (mindestens jedoch einen). Die String.Format-Funktion gibt die formatierte Zeichenfolge (Typ String) zurück.
Der erste Parameter ist das sogenannte Format vom Typ String. Dieses gibt die Form der formatierte Zeichenfolge an. Alle weiteren Parameter sind Daten (vom Typ Object, d.h. Sie können wirklich alles als Daten übergeben). Diese werden gemäß den Informationen im Format in die formatierte Zeichenfolge eingebaut. Wichtig sind hierbei die Formatelemente, die im Format in {geschweiften Klammern} stehen. Diese werden bei der Formatierung durch die jeweiligen Daten ersetzt. Die Zahl im Formatelement gibt an, welches Datum verwendet werden soll. (Nicht wundern: „Datum“ ist die Einzahl von „Daten“, das ist nur nicht so bekannt.) Die Zahl ist der Index dieses Datums. So wie bei Feldern ist dieser Index nullbasiert, das heißt, das erste Element trägt den Index 0 (nicht 1).
Dim Zahl1 As Integer = 3, Zahl2 As Integer = 6 Dim Text As String = String.Format("Zweimal {0} ergibt {1}.", Zahl1, Zahl2) MessageBox.Show(Text)
Zweimal 3 ergibt 6.
Hier haben wir das Format "Zweimal {0} ergibt {1}." sowie die Daten 3 und 6. Im Format werden nun die Formatelemente ("{0}" und "{1}") durch die jeweiligen Daten (3 und 6) ersetzt, genauer gesagt durch das Ergebnis der Umwandlung der Daten in den Datentyp String (hier "3" und "6"). So wird aus dem Format "Zweimal {0} ergibt {1}." die formatierte Zeichenfolge "Zweimal 3 ergibt 6."
Man kann das Codebeispiel von 3 Zeilen auf eine verkürzen, wenn man folgende Umstände ausnutzt:
- Auch Literale können als Parameter übergeben werden. Die Deklaration von Zahl1 und Zahl2 als Zwischenspeicher ist nicht nötig.
- Der Rückgabewert einer Funktion kann direkt wieder als Parameter eingesetzt werden, hier der Rückgabewert von String.Format als Parameter von MessageBox.Show.
Allerdings ist zu beachten, dass unter einer Codeverkürzung manchmal die Lesbarkeit leidet. Jetzt fragen Sie sich wahrscheinlich, warum wir einen solchen Aufwand betrieben haben, wenn wir es mit impliziter Konvertierung und dem geschickten Einsatz der Verkettungsoperation ("Zweimal " & Zahl1 & " ergibt" & Zahl2 & "."
) doch viel leichter gehabt hätten. Allerdings ist das nur die einfachste Einsatzmöglichkeit von Formatelementen. Es gibt auch komplexere Möglichkeiten, bei denen die Daten nicht einfach in String umgewandelt werden, sondern anhand bestimmter Regelsätze formatiert werden.
Erweiterte Syntax von Formatelementen
BearbeitenEin Beispiel hierfür ist die Ausgabe von formatierten Tabellen.
Dim i As Integer Dim myData(,) As Integer = {{203, 15, 100, 2}, {203, 16, 2000, 10}, {103, 1, 30000, 125}, {4, 135, 1, 6055}} 'Ausgabe Tabellentitel Console.WriteLine(String.Format("{0,-10}:{1,-10}:{2,-10}:{3,-10}", "Kategorie", "Typ", "Anzahl", "Preis")) 'Ausgabe Tabelle For i=0 To 3 Console.WriteLine(String.Format("{0,-10:0000}:{1,-10}:{2,10}:{3,10:C2}", myData(i, 0), myData(i, 1), myData(i, 2), myData(i, 3))) Next
Kategorie :Typ :Anzahl :Preis 0203 :15 : 100: 2,00 € 0203 :16 : 2000: 10,00 € 0103 :1 : 30000: 125,00 € 0004 :135 : 1:6.055,00 €
Sofort ins Auge fallen die Erweiterungen der Formatelemente. Der Index des Formatelements wird nun gefolgt von einem Komma und der eigentlichen Deifinition, wie dieses Element formatiert werden soll.
Betrachten wir den Index 0 des Tabellentitels: "{0,-10}". Die Ausage wird so formatiert, dass der übergebene Wert zehn Zeichen breit ist. Ein negatives Vorzeichen gibt eine linksbündige ein positives eine rechtsbündige Zeichenkette aus. Weiter im Code wird das Formatelement (Index 0) des Tabelleninhaltes nochmals um den Formatbezeichner geführt von einem Doppelpunkt erweitert.
Format- bezeichner |
Beschreibung | Beispiel |
---|---|---|
"0" | Ersetzt "0" sofern vorhanden durch die entsprechende Ziffer. Ist keine vorhanden so wird "0" eingetragen. | 123.456 > {0:0000} => 0123 123.456 > {0:0000.00} => 0123,46 |
"#" | Ersetzt "#" sofern vorhanden durch die entsprechende Ziffer. Ist keine vorhanden so wird nichts eingetragen. | 123.456 > {0:####} => 123 0.456 > {0:#.##} => ,46 |
"." | Bestimmt den Ort des Dezimaltrennzeichens. | 123.456 > {0:00.00} => 123.46 0.456 > {0:00.00} => 00,46 |
"," | Dient als Tausendertrennzeichen. | 1987654321 > {0:##,#} => 1.987.654.321 1987654321 > {0:#,#,,} => 1.987 |
"%" | Führt eine Multiplikation mit 100 durch und Stellt "%" voran. | 0.246 > {0:%0.00} => %24,60 0.246 > {0:%##} => %25 |
"C" oder "c" | Währungsformat für numerische Werte. | 123.456 > {0:C} => 123 € 123.456 {0:C2} => 123,46 € |
"D" oder "d" | Ganzzahlige numerische Werte. | 123 > {0:D} => 123 -123 > {0:D6} => -000123 |
"E0" "E+0" "E-0" "e0" "e+0" "e-0" |
Ermöglicht eine exponentielle Schreibweise. | 654321 > {0:#0.0e0} => 65,4e4 1234.56789 > {0:0.0##e+00} => 1,234e+03 |
"F" oder "f" | Dezimalstelle für ganzzahlige und dezimale Werte. | 123.456 > {0:F} => 123,46 123 > {0:F1} => 123,0 |
"G" oder "g" | Die Zahl wird je nach Typ in die kompakteste Festkomma- oder wissenschaftliche Schreibweise konvertiert. | 12345.6789 > {0:G} => 13245,6789 12345.6789 > {0:G7} => 13245,68 .0000012 > {0:G} => 1,2E-06 |
"N" oder "n" | Die Zahl wird in eine Zeichenfolge mit Tausendertrennzeichen konvertiert. | -12345.6789 > {0:N} => -12.345,68 -12345.6789 > {0:N1} => -12.345,7 |
"P" oder "p" | Die Zahl wird in eine Prozentangabe konvertiert. (Ähnlich "%") | .6789 > {0:P} => %67,89 .6789 > {0:P1} => %67,9 |
"R" oder "r" | Nur für Single oder Double. Stellt sicher, dass ein Wert in den gleichen numerischen Wert zurück konvertiert werden kann. | Math.Pi > {0:R} => 3,1415926535897931 1.623e-21 > {0:R} => 1,623E-21 |
"X" oder "x" | Nur für Ganzzahlige Typen. Konvertiert den Wert in hexdezimale Schreibweise. | &h1034e > {0:X} => 1034E 123456789 > {0:X10} => 00075BCD15 |
\ | Gibt das folgende Zeichen als Literal wieder und nicht als Formatbezeichner. | 123.456 > {0:\####.#\#} => #123.5# |
'string' | Übergibt ein Literal ohne Änderung an die Ausgabe. | 123.456 > {0:#.# 's'} => 123,5s 123.456 > {0:#' Zeilen'} => 123 Zeilen |
";" | Trennt unterschiedliche Formatierungen für positive Zahlen, negative Zahlen und Null. | 123 > {0:#0.0#;(#0.0#);-\0-} => 123,0 -123 > {0:#0.0#;(#0.0#);-\0-} => (123,0) 0 > {0:#0.0#;(#0.0#);-\0-} => -0- |
Im Folgenden wird die Funktion "ToString()" näher eläutert. Die oben abgebildete Tabelle ist allerdings schon ein kleiner Vorgriff. Wird der Formatbezeichner als Parameter an die Funktion übergeben, so liefert sie einen formatierten Sting zurück. Hinzu kommt, dass als weiterer Parameter noch ein FormatProvider übergeben werden kann, um auf länderspezifische Schreibweisen einzugehen.
'.ToString(String, FormatProvider) Dim num As Double = 123.456 Console.WriteLine(num.ToString(("C", System.Globalization.CultureInfo.CreateSpecificCulture("en-US")))
123.46 $
Informationen zum Thema „Erweiterte Syntax von Formatelementen“ sind leider noch nicht vorhanden, da sich bis jetzt noch keiner gefunden hat, der sich mit diesem Thema gut genug auskennt, um anderen dieses Thema näher zu bringen. Wenn Du dich mit diesem Thema auskennst, hilf bitte mit, dieses Kapitel zu vervollständigen.
Die To-Funktionen
BearbeitenFür die Umwandlung eines "unpassenden" Datentyps in einen passenden empfiehlt es sich auch, die Klasse der umzuwandelnden Variable im Objektbrowser zu betrachten. Dort finden sich dann im Member-Bereich gelegentlich Methoden mit den sprechenden Namen "ToDatentyp (...) As Datentyp, die nicht mit den ähnlich lautenden Funktionen der "Convert"-Klasse zu verwechseln sind. Manchmal findet sich auch als Namensbestandteil der Funktion nicht der Zieldatentyp, sondern der Zweck der Umwandlung, etwa in der Klasse "DateTime" die Funktionen "ToFileTime() As Long" oder "ToOADate() As Double".
Haupsächlich finden sich diese Funktionen außerhalb des Bereichs, in dem "CInt" & Co Anwendung finden. So definiert die Klasse "Decimal", die kein Ziel einer Konversion mit C-Funktionen darstellt, ohne "ToString"-Funktionen (siehe der nächste Abschnitt) insgesamt 11 "To-Funktionen" (ToByte, ToDouble, ToInt16 usw.).
Funktionen diesen Typs finden sich auch außerhalb der einfachen Datentypen, so definieren sowohl die Klasse "ArrayList" als auch die generische Klasse "List(Of T)" eine Methode "ToArray", die ein untypisiertes bzw typisiertes Array zurückgeben.
Sonderfall: ToString-Funktion
BearbeitenJeder Datentyp ist von System.Object abgeleitet und verfügt daher über dessen ToString-Methode, Diese gibt einen Ausdruck vom Typ "String" zurück. Bei den einfachen Wert-Typen (Byte, Short, Integer, Long, Date, Boolean, Char usw.) gibt diese Funktion die String-Entsprechung dieser Typen in einer Standardformatierung zurück, bei Variablen, die ein Objekt vom Typ "String" darstellen, ist der Rückgabewert der String selbst. Insoweit kann die Funktion zur Typumwandlung benutzt werden. "Hinter den Kulissen" ist die ToString-Funktion auch bei der Typumwandlung innerhalb der String.Format-Methode (siehe vorstehend) beteiligt. Die ToString-Methode kann dabei für den konkreten Datentyp nicht nur überschrieben, sondern auch überladen sein und dann Formatparameter entgegennehmen.
Bei Objekten, die Instanzen von komplexeren, auch selbstprogrammierten Klassen darstellen, gibt aber "ToString" in der Standardimplementierung den Klassennamen zurück, oder im Falle der Überschreibung einen vom Programmierer der Klasse festgelegten Wert. Bei Objekten z.B., die über eine "Name"-Eigenschaft verfügen, kann dies der Wert dieser Eigenschaft sein. Ein Objekt der Klasse StringBuilder, mit der Teilstrings verkettet werden können, liefert auf den Aufruf von ToString den gesamten verketteten String zurück. Es handelt sich also um eine Routine die nicht speziell der Typumwandlung dient, sondern - diesen Zweck teilt sie mit String.Format - um eine Methode, die eine sinnvolle Stringrepräsentation des zugrundeliegenden Objekts liefert.
Erweiternde und eingrenzende Konvertierungen
BearbeitenJeder Datentyp hat einen anderen Wertebereich. Integer hat etwa einen größeren Wertebereich als Byte. Wenn Sie einen Byte-Wert nach Integer konvertieren, spricht man deshalb von einer erweiternden Konvertierung, andersrum von einer eingrenzenden Konvertierung. Man sagt auch, Byte kann zu Integer erweitert werden. Bei einer erweiternden Konvertierung kann nichts Schlimmes passieren, denn alle Werte des Ausgangsdatentyps können in Werte des Zieldatentyps umgewandelt werden. Bei eingrenzenden Konvertierungen sieht das schon ganz anders aus. Hier treten des Öfteren Überlaufsfehler auf, wenn Sie etwa versuchen, den Integer-Wert 100000 nach Byte zu konvertieren, also einzugrenzen. 100000 ist aber zu groß für Byte, es kommt zum Überlauf.
Gute Entwicklungsumgebungen warnen Sie, wenn Sie eine eingrenzende Konvertierung vornehmen wollen. Allerdings warnt Sie etwa das Microsoft-Produkt nicht bei der eingrenzenden Konvertierung von String in irgendeinen Zahlentyp, etwa Long. Werte wie "abc" lassen sich nämlich nicht auf Long abbilden, sodass es auch hier zu einem Laufzeitfehler kommt. Wie Sie die eingrenzenden Konvertierungen unter Kontrolle bringen, verrät Ihnen das Kapitel Fehlerbehandlung. Dort geht es darum, wie Sie Fehler, die zum Beispiel bei eingrenzenden Konvertierungen entstehen, abfangen können, bevor diese Ihr Programm zum Stillstand bringen.
Boolean
BearbeitenAuch Boolean-Werte können in andere Werte umgewandelt werden. Bei Umwandlung in eine Zahl wird False zu 0 und True zu -1, bei Umwandlung in einen String wird False zu "False" und True zu "True".
Operatoren
BearbeitenCType-Operator
BearbeitenDer CType-Operator verhält sich ähnlich wie die oben beschriebenen C-Funktionen, er ist nur universeller und übernimmt daher zur Bestimmung des Ziel-Datentyps einen zweiten Parameter. Hinter dem zu konvertierenden Ausdruck vom Typ "Object" wird ein Parameter vom Typ "System.Type" eingefügt, der den Datentyp des Ergebnisses bezeichnet. Zum Vergleich:
(mit C-Funktion:) Dim MyLong as Long = CLng(MyIntegerVariable)
(mit CType:) Dim MyLong as Long = CType(MyIntegerVariable, Long)
Eine Umwandlung mit CType ist aber nur möglich, wenn sie von einer der beteiligten Klassen definiert ist. Für die "einfachen" Datentypen wie String, Integer usw. ist dies aber im Umfang der obern dargestellten C-Funktionen möglich.
Der Programmierer einer Klasse kann aber (und damit geht CType über die C-Funktionen hinaus) durch Überschreiben des CType-Operators für diese Klasse und andere Klassen eine Umwandlungsroutine definieren und implementieren. Die ist z.B. bei Visual Studio dann im Objectbrowser als "Operator CType (xClass) As yClass" zu sehen.
DirectCast und TryCast
BearbeitenKeine Umwandlungsfunktionen im eigentlichen Sinn haben die Schlüsselwörter "DirectCast" und "TryCast".
"DirectCast" (Aufruf: "x = DirectCast(y, [Zieltyp für x])") setzt voraus, dass Ausgangs- und Zieltyp voneinander erben oder (bei Beteiligung von Interfaces) einer den anderen implementiert. Hintergrund ist der, dass die Umwandlung sich nicht auf das Objekt und seine Daten selbst bezieht, sondern auf den Typ der Objektvariablen. So gelingt der Aufruf "Dim a As [Basisklasse] = DirectCast (Object_xyz, [Basisklasse]) immer, der Aufruf "Dim a As [AbgeleiteteKlasse] = DirectCast (Object_xyz, [Abgeleitete Klasse])" aber nur dann, wenn "Object_xyz" auf ein Objekt der abgeleiteten Klasse oder eines verweist, dessen Klasse wiederum von der abgeleiteten Klasse weiter abgeleitet ist.
Zum Beispiel:
'Gegeben seien die Basisklasse "Haustier", die davon abgeleitete Klasse "Katze" und die von 'Katze abgeleitete Klasse "Siamkatze" Dim a As New Siamkatze Dim b As New Katze 'Die folgenden Aufrufe gelingen immer, weil a wie b Haustiere sind: Dim c As Haustier = DirectCast(a, Haustier) Dim d As Haustier = DirectCast(b, Haustier) 'Der folgende Aufruf gelingt, weil d nur eine andere Variable für das hinter der Variable b 'befindliche Objekt vom Typ Katze ist: Dim f As Katze = DirectCast(d, Katze) 'Der folgende Aufruf gelingt ebenfalls, weil c auf dasselbe Objekt wie a verweist, eine 'Siamkatze, die auch eine Katze ist: Dim e As Katze = DirectCast(c, Katze) 'Der folgende Aufruf gelingt, weil c auf dasselbe Objekt wie a verweist, nämlich eine Siamkatze Dim g As Siamkatze = DirectCast(c, Siamkatze) 'Der folgende Aufruf misslingt, weil d auf dasselbe Objekt wie b verweist, nämlich eine Katze, 'die nicht zugleich eine Siamkatze ist: Dim h As Siamkatze = DirectCast(d, Siamkatze)
"TryCast" verhält sich ähnlich, nimmt aber nur Verweistypen wie "String" als Parameter entgegen und damit nicht Variablen von Werttypen wie "Integer" etc. Während "DirectCast" beim Fehlschlagen einer Umwandlung eine InvalidCastException auslöst, gibt "TryCast" in diesen Fällen "Nothing" zurück.