ein Projekt von goloroden.de

Ausdrücke

Konvertieren

Bei der Division und Modulodivision von Typen wurde erwähnt, dass das Ergebnis einer Verknüpfung von zwei Operanden mit Hilfe eines arithmetischen Operators immer dem Typ der beiden Operanden entspricht, weshalb es insbesondere bei der Division von ganzzahligen Datentypen zu Problemen kommen kann, da der Dezimalteil verloren geht.

Die vermeintliche Lösung, das Ergebnis einer solchen Division einer Variablen vom Typ float oder double zuzuweisen, erweist sich bei näherer Betrachtung als unzureichend, da die Dezimalstellen des Ergebnisses bereits im Speicher abgeschnitten werden, noch bevor die Zuweisung an die aufnehmende Variable ausgeführt wird.

Eine Möglichkeit, diesem Problem zu begegnen, liegt darin, mindestens einen der Operanden in einen Typ zu wandeln, der über Dezimalstellen verfügt. Da es aber nicht in jedem Fall möglich ist, einen Operanden von vornherein als entsprechenden Typ zu deklarieren, muss dies gegebenenfalls während der Ausführung zur Laufzeit geschehen. Dieser Vorgang wird als konvertieren oder casten bezeichnet.

Die einfachste Möglichkeit, einen Typ in einen anderen zu konvertieren, ist, den eigentlichen Wert dem neuen Typen zuzuweisen. Da hierbei die Umwandlung in den neuen Typ implizit geschieht, wird diese Art der Konvertierung als implizite Konvertierung bezeichnet.
C#
1
2
3
4
5
6
// Assign a value to an int variable.
int x = 23;

// Assign the value to a long variable. The value is
// implicitly casted from int to long.
long y = x;
Sofern der Wertebereich des Typs, in den konvertiert wird, umfangreicher ist als der des Typs, der den ursprünglichen Wert enthält, funktioniert dieses Verfahren ohne weiteres. Auf diese Art können beispielsweise int in long und float in double konvertiert werden. Beim Versuch, eine Konvertierung in umgekehrter Richtung durchzuführen, meldet C# allerdings einen Fehler, da potenziell ein Werteverlust eintreten könnte.

Soll eine solche Konvertierung dennoch ausgeführt werden, muss dies mit Hilfe einer expliziten Konvertierung geschehen. Bei dieser wird dem umzuwandelnden Wert der Typ, in den konvertiert wird, innerhalb runder Klammern vorangestellt.
C#
1
2
3
4
5
6
// Assign a value to a long variable.
long x = 23;

// Assign the value to an int variable. The value needs to
// be casted explicitly.
int y = (int)x;
Die explizite Konvertierung ermöglicht auch die Division zweier Ganzzahlen unter Beibehaltung des Dezimalteils des Ergebnisses. Dazu muss lediglich einer der beiden Operanden in einen Dezimaltyp konvertiert werden.
C#
1
2
3
4
5
6
7
8
9
int x = 23;
int y = 42;

// The quotient is 0, since no cast has been done.
float quotient = x / y;

// The quotient is 0.547619, since one of the operands has
// been casted explicitly to a decimal type.
quotient = (float)x / y;

Boxing

Im Rahmen der Vererbung wurde erwähnt, dass alle Typen von object ableiten. Aus diesem Grund ist es möglich, jeden beliebigen Typ nach object zu konvertieren, sogar dann, wenn es sich bei dem ursprünglichen Typ um einen Werte- und nicht um einen Verweistyp handelt.

Während sich bei einem Verweistyp lediglich der Typ des Verweises ändert, ändert sich bei einem Wertetyp zusätzlich die Art, wie der Wert gespeichert wird, da die Anwendung bei einem Verweistyp nur mit einem Verweis auf die eigentlichen Daten, bei einem Wertetyp aber direkt mit den eigentlichen Daten arbeitet. Daher ist es notwendig, einen Wertetyp, der nach object konvertiert werden soll, zunächst in einen zusätzlichen Verweistyp zu verpacken, auf den dann wiederum ein Verweis vom Typ object angelegt werden kann.

Dieses Verpacken wird als Boxing bezeichnet und von C# intern automatisch durchgeführt, sobald ein Wertetyp in einen Verweistyp konvertiert wird. Obwohl man sich also nicht händisch um das Boxing kümmern muss, sollte man sich während der Entwicklung dieses Vorgangs im Hintergrund immer bewusst sein, da dieser nicht nur zusätzlichen Speicher verbraucht, sondern auch Zeit benötigt. Insofern sollte Boxing nur mit Bedacht und gezielt an einigen Stellen eingesetzt werden.

Nachdem ein Wertetyp in einen Verweistyp verpackt wurde, kann dieser Vorgang auch wieder umgekehrt werden, um aus dem Verweistyp den ursprünglichen Wertetyp zu erhalten. Dies wird als Unboxing bezeichnet und folgt den gleichen Regeln wie das Boxing.
C#
1
2
3
4
5
6
7
int valueType = 23;

// Box the value type and create a reference type.
object referenceType = valueType;

// Unbox the reference type.
valueType = (int)referenceType;

Benutzerdefiniertes Konvertieren

Prinzipiell können mit diesen Möglichkeiten zum einen beliebige Typen in object konvertiert werden, zum anderen können Typen ineinander konvertiert werden, die über eine gemeinsame Basis verfügen oder die in einer Vererbungshierarchie zueinander stehen. Gelegentlich kann es jedoch nützlich sein, eine eigene Konvertierung definieren zu können, um beispielsweise eine komplexe Zahl mit Hilfe ihres Absolutbetrags in float? zu konvertieren.

Diese Konvertierung kann so wohl implizit wie auch explizit implementiert werden. In beiden Fällen muss die bestehende Klasse durch eine weitere Operatorüberladung ergänzt werden, wobei der Zieltyp der Konvertierung als Operatorname dient. Außerdem muss eines der beiden Schlüsselwörter implicit und explicit angegeben werden, um zu definieren, ob die Konvertierung in den Zieltyp implizit ausgeführt werden kann, oder ob zwingend eine explizite Konvertierung benötigt wird.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Executes when storing begins.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The event arguments.</param>
    public delegate void StoringEventHandler(
        object sender, EventArgs eventArguments);

    /// <summary>
    /// Executes when storing has finished.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The event arguments.</param>
    public delegate void StoredEventHandler(
        object sender, EventArgs eventArguments);

    /// <summary>
    /// Executes when restoring begins.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The event arguments.</param>
    public delegate void RestoringEventHandler(
        object sender, EventArgs eventArguments);

    /// <summary>
    /// Executes when restoring has finished.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The event arguments.</param>
    public delegate void RestoredEventHandler(
        object sender, EventArgs eventArguments);

    /// <summary>
    /// Represents a complex number.
    /// </summary>
    public sealed class ComplexNumber : IPersistable
    {
        #region Properties
        #endregion

        #region Events
        #endregion

        #region Operators
        // ...

        /// <summary>
        /// Casts the specified complex number to float?.
        /// </summary>
        /// <param name="complexNumber">The complex number
        /// that shall be casted.</param>
        /// <returns>A float? representation of the specified
        /// complex number.</returns>
        public static implicit operator float?(
            ComplexNumber complexNumber)
        {
            // Return the complex number as float? by using
            // its absolute value.
            return complexNumber.AbsoluteValue;
        }
        #endregion

        #region Methods
        #endregion

        #region Constructors
        #endregion
    }
}
Ein Rückgabetyp muss im Gegensatz zu den bisherigen Operatorüberladungen nicht angegeben werden, da sich dieser aus dem Operator an sich bereits ergibt. Zu beachten ist bei der Definition benutzerdefinierter Konvertierungsoperatoren noch, dass es nicht für einen Zieltyp zugleich so wohl einen impliziten wie auch einen expliziten Operator geben kann. Die Definition mehrerer Konvertierungsoperatoren ist nur möglich, sofern sich diese durch ihren Zieltyp unterscheiden.

Konvertierbarkeit

Obwohl C# zahlreiche Möglichkeiten bietet, zwischen verschiedenen Typen zu konvertieren, kann es dennoch vorkommen, dass ein bestimmter Typ schlichtweg nicht in einen anderen Typ konvertierbar ist. Wird ein solcher Versuch trotzdem unternommen, tritt ein Fehler auf und die Ausführung der Anwendung wird abgebrochen.

Um dies zu verhindern, enthält C# drei Schlüsselwörter, mit denen geprüft werden kann, ob sich ein Typ in einen bestimmten Zieltyp konvertiert lässt. Das einfachste dieser Schlüsselwörter ist typeof, das ein Objekt der Klasse Type zurückgibt, das Informationen zu dem jeweiligen Typ enthält. Eine Analyse dieses Typobjekts ermöglicht dann im weiteren Verlauf, zu bestimmen, ob und auf welche Art konvertiert werden kann.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Get type information on the Program type.
            Type type = typeof(Program);

            // Print the type's full name to the console.
            Console.WriteLine(type.FullName);
        }
    }
}
Häufig ist der Einsatz eines kompletten Typobjekts allerdings zu aufwändig, da nur von Interesse ist, ob ein Typ überhaupt in einen bestimmten Zieltyp konvertiert werden kann. Dazu dient das Schlüsselwort is, das je nach Konvertierbarkeit true oder false an den Aufrufer zurückgibt.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define a string and store it within an object
            // reference.
            object value = "Hello world!";

            // Execute code depending on the type of the
            // value.
            if (value is string)
            {
                // Cast the value to a string.
                string valueAsString = (string)value;
                
                // TODO gr: Do something ...
                //          2008-01-03
            }
        }
    }
}
Schließlich gibt es noch das Schlüsselwort as, das prinzipiell ebenfalls eine explizite Konvertierung durchführt, im Fehlerfall aber nicht die Ausführung der Anwendung abbricht, sondern das Literal null zurückgibt.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define a string and store it within an object
            // reference.
            object value = "Hello world!";

            // Try to cast the value to string.
            string valueAsString = value as string;

            // If the cast was successful, execute some
            // code.
            if (valueAsString != null)
            {
                // TODO gr: Do something ...
                //          2008-01-03
            }
        }
    }
}