Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


C# von Eric Gunnerson
Die neue Sprache für Microsofts .NET-Plattform
C# - Zum Katalog
gp Kapitel 25 Benutzerdefinierte Konvertierung
  gp 25.1 Ein einfaches Beispiel
  gp 25.2 Prä- und Postkonvertierungen
  gp 25.3 Konvertierungen zwischen Strukturen
  gp 25.4 Klassen und Prä-/Postkonvertierungen
  gp 25.5 Entwurfsrichtlinien
  gp 25.6 So funktioniert’s

Kapitel 25 Benutzerdefinierte Konvertierung

C# ermöglicht die Definition von Konvertierungen zwischen Klassen oder Strukturen (struct-Schlüsselwort) und anderen Objekten im System. Bei benutzerdefinierten Konvertierungen handelt es sich immer um statische Funktionen, die entweder einen Parameter tragen oder als Rückgabewert das Objekt zurückgeben, in dem sie deklariert sind. Konvertierungen können demnach nicht zwischen zwei vorhandenen Typen deklariert werden, was die Sprache vereinfacht.


Galileo Computing

25.1 Ein einfaches Beispiel  downtop

In diesem Beispiel wird eine Struktur zur Handhabung römischer Ziffern implementiert. Es könnte auch eine Klasse geschrieben werden.

using System;
using System.Text;
struct RomanNumeral
{
    public RomanNumeral(short value)
    {
        if (value > 5000)
            throw(new ArgumentOutOfRangeException());

        this.value = value;
    }
    public static explicit operator RomanNumeral(
        short value)
    {
        RomanNumeral    retval;
        retval = new RomanNumeral(value);
        return(retval);
    }

    public static implicit operator short(
    RomanNumeral roman)
    {
        return(roman.value);
    }

    static string NumberString(
    ref int value, int magnitude, char letter)
    {
        StringBuilder    numberString = new StringBuilder();

        while (value >= magnitude)
        {
            value -= magnitude;
            numberString.Append(letter);
        }
        return(numberString.ToString());
    }

    public static implicit operator string(
    RomanNumeral roman)
    {
        int        temp = roman.value;

        StringBuilder retval = new StringBuilder();

        retval.Append(RomanNumeral.NumberString(ref temp, 1000, 'M'));
        retval.Append(RomanNumeral.NumberString(ref temp, 500, 'D'));
        retval.Append(RomanNumeral.NumberString(ref temp, 100, 'C'));
        retval.Append(RomanNumeral.NumberString(ref temp, 50, 'L'));
        retval.Append(RomanNumeral.NumberString(ref temp, 10, 'X'));
        retval.Append(RomanNumeral.NumberString(ref temp, 5, 'V'));
        retval.Append(RomanNumeral.NumberString(ref temp, 1, 'I'));

        return(retval.ToString());
    }

    private short value;
}
class Test
{
    public static void Main()
    {
        RomanNumeral numeral = new RomanNumeral(12);

        numeral = (RomanNumeral) 165;

        Console.WriteLine("Roman as int: {0}", (int)numeral);
        Console.WriteLine("Roman as string: {0}", (string)numeral);

        short s = numeral;
    }
}

Hier wird mit der Struktur eine Erstellungsroutine deklariert, die einen short-Wert tragen kann und außerdem eine Konvertierung von einem integer-Wert in eine römische Ziffer (RomanNumeral) deklariert. Die Konvertierung wird als explicit deklariert, da eine Ausnahme auftreten kann, wenn die angegebene Zahl größer ist als der von struct unterstützte Bereich. Es liegt eine als implicit deklarierte Konvertierung in einen short-Wert vor, da der Wert in einem RomanNumeral immer in einen short-Wert passt. Abschließend findet eine Konvertierung in einen string-Wert statt, um die Zahl als römische Ziffer darzustellen.

Wenn eine Instanz der Struktur erstellt wird, kann die Erstellungsroutine zum Setzen des Wertes verwendet werden. Der integer-Wert kann über eine explizite Konvertierung in eine RomanNumeral umgewandelt werden. Um die romanisierte Version von RomanNumeral zu erhalten, könnte folgender Code verwendet werden:

Console.WriteLine(roman);

Anschließend werden Sie vom Compiler darüber informiert, dass eine unklare Konvertierung vorhanden ist. Die Klasse umfasst als implicit deklarierte Konvertierungen in short- und string-Werte, und Console.WriteLine() weist Überladungen für beide Versionen auf, daher weiß der Compiler nicht, welche Version aufgerufen werden soll.

Im Beispiel wird zur Klärung eine explizite Typumwandlung verwendet, auch wenn dies etwas unschön ist. Da diese Struktur hauptsächlich zum Ausgeben der romanisierten Schreibweise eingesetzt werden würde, ist es sinnvoll, die Konvertierung des integer-Wertes als explicit zu deklarieren, damit die Konvertierung in einen string-Wert die einzige implizite Konvertierung ist.


Galileo Computing

25.2 Prä- und Postkonvertierungen  downtop

Im vorangegangenen Beispiel stimmten die grundlegenden Typen, die in RomanNumeral und zurück konvertiert wurden, exakt mit den Typen überein, die in der Struktur selbst deklariert wurden. Die benutzerdefinierten Konvertierungen können auch in Situationen verwendet werden, in denen der Quell- oder der Zieltyp nicht exakt mit den Typen in der Konvertierungsfunktion übereinstimmen.

Wenn Quell- oder Zieltyp nicht exakt übereinstimmen, muss die geeignete standardmäßige (d. h. integrierte) Konvertierung vorhanden sein, um die Konvertierung vom Quelltyp in den Quelltyp der benutzerdefinierten Konvertierung und/oder den Zieltyp der benutzerdefinierten Konvertierung durchzuführen. Die Art der Konvertierung (implicit oder explicit) muss darüber hinaus kompatibel sein.

Vielleicht wird dies durch ein Beispiel ein wenig klarer. Die Zeile

short s = numeral;

des vorangegangenen Beispiels ruft die implizite benutzerdefinierte Konvertierung direkt auf. Da es sich um eine implizite Verwendung der benutzerdefinierten Konvertierung handelt, kann am Ende ebenfalls eine weitere implizite Konvertierung vorhanden sein:

int i = numeral;

Hier wird die implizite Konvertierung von RomanNumeral in short durchgeführt, gefolgt von einer impliziten Konvertierung von short in long.

Im expliziten Fall wurde im Beispiel die folgende Konvertierung verwendet:

numeral = (RomanNumeral) 165;

Da die Verwendung explicit lautet, wird die explizite Konvertierung von int in RomanNumeral verwendet. Darüber hinaus kann eine zusätzliche explizite Konvertierung vor Aufruf der benutzerdefinierten Konvertierung durchgeführt werden:

long bigvalue = 166;
short smallvalue = 12;
numeral = (RomanNumeral) bigvalue;
numeral = (RomanNumeral) smallvalue;

Bei der ersten Konvertierung wird der long-Wert über eine explizite Konvertierung in einen integer-Wert umgewandelt, anschließend wird die benutzerdefinierte Konvertierung aufgerufen. Die zweite Konvertierung ist ähnlich, abgesehen davon, dass eine implizite Konvertierung durchgeführt wird, bevor die explizite benutzerdefinierte Konvertierung ausgeführt wird.


Galileo Computing

25.3 Konvertierungen zwischen Strukturen  downtop

Benutzerdefinierte Konvertierungen, die statt grundlegender Typen Klassen oder Strukturen umfassen, funktionieren ähnlich, es müssen jedoch einige weitere Punkte beachtet werden. Da die Benutzerkonvertierung entweder im Quell- oder Zieltyp definiert werden kann, ist etwas mehr Entwurfsarbeit erforderlich, und die Operation ist etwas komplexer. Detailliertere Informationen finden Sie im Abschnitt »So funktioniert’s« weiter unten in diesem Kapitel.

Dem RomanNumeral-Beispiel im letzten Abschnitt kann eine Struktur zur Handhabung binärer Zahlen hinzugefügt werden:

using System;
using System.Text;
struct RomanNumeral
{
    public RomanNumeral(short value)
    {
        if (value > 5000)
            throw(new ArgumentOutOfRangeException());

        this.value = value;
    }
    public static explicit operator RomanNumeral(
    short value)
    {
        RomanNumeral    retval;
        retval = new RomanNumeral(value);
        return(retval);
    }

    public static implicit operator short(
    RomanNumeral roman)
    {
        return(roman.value);
    }

    static string NumberString(
    ref int value, int magnitude, char letter)
    {
        StringBuilder    numberString = new StringBuilder();

        while (value >= magnitude)
        {
            value -= magnitude;
            numberString.Append(letter);
        }
        return(numberString.ToString());
    }

    public static implicit operator string(
    RomanNumeral roman)
    {
        int        temp = roman.value;

        StringBuilder retval = new StringBuilder();

        retval.Append(RomanNumeral.NumberString(ref temp, 1000, 'M'));
        retval.Append(RomanNumeral.NumberString(ref temp, 500, 'D'));
        retval.Append(RomanNumeral.NumberString(ref temp, 100, 'C'));
        retval.Append(RomanNumeral.NumberString(ref temp, 50, 'L'));
        retval.Append(RomanNumeral.NumberString(ref temp, 10, 'X'));
        retval.Append(RomanNumeral.NumberString(ref temp, 5, 'V'));
        retval.Append(RomanNumeral.NumberString(ref temp, 1, 'I'));

        return(retval.ToString());
    }

    private short value;
}
struct BinaryNumeral
{
    public BinaryNumeral(int value)
    {
        this.value = value;
    }
    public static implicit operator BinaryNumeral(
    int value)
    {
        BinaryNumeral    retval = new BinaryNumeral(value);
        return(retval);
    }

    public static implicit operator int(
    BinaryNumeral binary)
    {
        return(binary.value);
    }

    public static implicit operator string(
    BinaryNumeral binary)
    {
        StringBuilder    retval = new StringBuilder();

        return(retval.ToString());
    }

    private int value;
}
class Test
{
    public static void Main()
    {
        RomanNumeral    roman = new RomanNumeral(12);
        BinaryNumeral    binary;
        binary = (BinaryNumeral)(int)roman;
    }
}

Die Klassen können zusammen verwendet werden, da sie jedoch keine Informationen übereinander besitzen, sind einige Extraeingaben erforderlich. Zur Konvertierung von RomanNumeral in BinaryNumeral muss zunächst eine Konvertierung in einen int-Wert durchgeführt werden.

Es wäre schöner, die Main()-Funktion als

binary = roman;
roman = (RomanNumeral) binary;

zu schreiben und die Typen wie integrierte Typen aussehen zu lassen. Es ist jedoch so, dass RomanNumeral über einen kleineren Bereich verfügt als binary, daher ist in diesem Abschnitt eine explizite Konvertierung erforderlich.

Um dies zu erreichen, muss entweder für RomanNumeral oder für die BinaryNumeral-Klasse eine benutzerdefinierte Konvertierung ausgeführt werden. In diesem Fall wird die Konvertierung für die RomanNumeral-Klasse durchgeführt. Die Gründe hierfür werden im Abschnitt »Entwurfsrichtlinien« am Ende dieses Kapitels erläutert.

Die Klassen werden folgendermaßen geändert, es werden zwei Konvertierungen hinzugefügt:

using System;
using System.Text;
struct RomanNumeral
{
    public RomanNumeral(short value)
    {
        if (value > 5000)
            throw(new ArgumentOutOfRangeException());

        this.value = value;
    }
    public static explicit operator RomanNumeral(
    short value)
    {
        RomanNumeral    retval;
        retval = new RomanNumeral(value);
        return(retval);
    }

    public static implicit operator short(
    RomanNumeral roman)
    {
        return(roman.value);
    }

    static string NumberString(
    ref int value, int magnitude, char letter)
    {
        StringBuilder    numberString = new StringBuilder();

        while (value >= magnitude)
        {
            value -= magnitude;
            numberString.Append(letter);
        }
        return(numberString.ToString());
    }

    public static implicit operator string(
    RomanNumeral roman)
    {
        int        temp = roman.value;

        StringBuilder retval = new StringBuilder();

        retval.Append(RomanNumeral.NumberString(ref 
temp, 1000, 'M'));
        retval.Append(RomanNumeral.NumberString(ref temp, 500, 'D'));
        retval.Append(RomanNumeral.NumberString(ref temp, 100, 'C'));
        retval.Append(RomanNumeral.NumberString(ref temp, 50, 'L'));
        retval.Append(RomanNumeral.NumberString(ref temp, 10, 'X'));
        retval.Append(RomanNumeral.NumberString(ref temp, 5, 'V'));
        retval.Append(RomanNumeral.NumberString(ref temp, 1, 'I'));

        return(retval.ToString());
    }
    public static implicit operator BinaryNumeral(RomanNumeral roman)
    {
        return(new BinaryNumeral((short) roman));
    }

    public static explicit operator RomanNumeral(
    BinaryNumeral binary)
    {
        return(new RomanNumeral((short) binary));
    }

    private short value;
}
struct BinaryNumeral
{
    public BinaryNumeral(int value)
    {
        this.value = value;
    }
    public static implicit operator BinaryNumeral(
    int value)
    {
        BinaryNumeral    retval = new BinaryNumeral(value);
        return(retval);
    }

    public static implicit operator int(
    BinaryNumeral binary)
    {
        return(binary.value);
    }

    public static implicit operator string(
    BinaryNumeral binary)
    {
        StringBuilder    retval = new StringBuilder();

        return(retval.ToString());
    }

    private int value;
}
class Test
{
    public static void Main()
    {
        RomanNumeral    roman = new RomanNumeral(122);
        BinaryNumeral    binary;
        binary = roman;
        roman = (RomanNumeral) binary;
    }
}

Mit diesen zusätzlichen Konvertierungen können die Konvertierungen zwischen den beiden Typen nun durchgeführt werden.


Galileo Computing

25.4 Klassen und Prä-/Postkonvertierungen  downtop

Wie bei den grundlegenden Typen können Klassen Standardkonvertierungen aufweisen, die entweder vor oder nach der benutzerdefinierten Konvertierung oder sogar vor und nach der benutzerdefinierten Konvertierung erfolgen können. Die einzigen Standardkonvertierungen, die Klassen betreffen, sind jedoch Konvertierungen in eine Basisklasse oder eine abgeleitete Klasse, deshalb werden auch nur diese berücksichtigt.

Bei impliziten Konvertierungen ist dies recht einfach, die Konvertierung erfolgt in drei Schritten:

1. Optionale Konvertierung von einer abgeleiteten Klasse in die Quellklasse der benutzerdefinierten Konvertierung
2. Benutzerdefinierte Konvertierung
3. Optionale Konvertierung von der Zielklasse der benutzerdefinierten Konvertierung in eine Basisklasse

Zur Veranschaulichung wird das Beispiel so abgeändert, dass anstelle von Strukturen Klassen verwendet werden und eine neue Klasse hinzugefügt wird, die von RomanNumeral abgeleitet ist:

using System;
using System.Text;
class RomanNumeral
{
    public RomanNumeral(short value)
    {
        if (value > 5000)
            throw(new ArgumentOutOfRangeException());

        this.value = value;
    }
    public static explicit operator RomanNumeral(
    short value)
    {
        RomanNumeral    retval;
        retval = new RomanNumeral(value);
        return(retval);
    }

    public static implicit operator short(
    RomanNumeral roman)
    {
        return(roman.value);
    }

    static string NumberString(
    ref int value, int magnitude, char letter)
    {
        StringBuilder    numberString = new StringBuilder();

        while (value >= magnitude)
        {
            value -= magnitude;
            numberString.Append(letter);
        }
        return(numberString.ToString());
    }

    public static implicit operator string(
    RomanNumeral roman)
    {
        int        temp = roman.value;

        StringBuilder retval = new StringBuilder();

        retval.Append(RomanNumeral.NumberString(ref temp, 1000, 'M'));
        retval.Append(RomanNumeral.NumberString(ref temp, 500, 'D'));
        retval.Append(RomanNumeral.NumberString(ref temp, 100, 'C'));
        retval.Append(RomanNumeral.NumberString(ref temp, 50, 'L'));
        retval.Append(RomanNumeral.NumberString(ref temp, 10, 'X'));
        retval.Append(RomanNumeral.NumberString(ref temp, 5, 'V'));
        retval.Append(RomanNumeral.NumberString(ref temp, 1, 'I'));

        return(retval.ToString());
    }
    public static implicit operator BinaryNumeral(RomanNumeral roman)
    {
        return(new BinaryNumeral((short) roman));
    }

    public static explicit operator RomanNumeral(
    BinaryNumeral binary)
    {
        return(new RomanNumeral((short)(int) binary));
    }

    private short value;
}
class BinaryNumeral
{
    public BinaryNumeral(int value)
    {
        this.value = value;
    }
    public static implicit operator BinaryNumeral(
    int value)
    {
        BinaryNumeral    retval = new BinaryNumeral(value);
        return(retval);
    }

    public static implicit operator int(
    BinaryNumeral binary)
    {
        return(binary.value);
    }

    public static implicit operator string(
    BinaryNumeral binary)
    {
        StringBuilder    retval = new StringBuilder();

        return(retval.ToString());
    }

    private int value;
}
class RomanNumeralAlternate : RomanNumeral
{
    public RomanNumeralAlternate(short value): base(value)
    {
    }

    public static implicit operator string(
    RomanNumeralAlternate roman)
    {
        return("NYI");
    }
}
class Test
{
    public static void Main()
    {
            // Abschnitt mit impliziter Konvertierung
        RomanNumeralAlternate    roman;
        roman = new RomanNumeralAlternate(55);

        BinaryNumeral binary = roman;
            // Abschnitt mit expliziter Konvertierung
        BinaryNumeral binary2 = new BinaryNumeral(1500);
        RomanNumeralAlternate roman2;

        roman2 = (RomanNumeralAlternate) binary2;
    }
}

Die Operation der als implicit deklarierten Konvertierung in BinaryNumeral verläuft wie erwartet; es erfolgt eine implizite Konvertierung des roman-Elements von RomanNumeralAlternate in RomanNumeral, anschließend wird die benutzerdefinierte Konvertierung von RomanNumeral in BinaryNumeral durchgeführt.

Der Abschnitt mit der expliziten Konvertierung könnte einigen Programmierern Kopfschmerzen bereiten. Die benutzerdefinierte Funktion von BinaryNumeral in RomanNumeral gibt ein RomanNumeral zurück, und die Postkonvertierung in RomanNumeralAlternate kann niemals erfolgreich sein.

Die Konvertierung könnte folgendermaßen umgeschrieben werden:

using System;
using System.Text;
class RomanNumeral
{
    public RomanNumeral(short value)
    {
        if (value > 5000)
            throw(new ArgumentOutOfRangeException());

        this.value = value;
    }
    public static implicit operator short(
    RomanNumeral roman)
    {
        return(roman.value);
    }

    static string NumberString(
    ref int value, int magnitude, char letter)
    {
        StringBuilder    numberString = new StringBuilder();

        while (value >= magnitude)
        {
            value -= magnitude;
            numberString.Append(letter);
        }
        return(numberString.ToString());
    }

    public static implicit operator string(
    RomanNumeral roman)
    {
        int        temp = roman.value;

        StringBuilder retval = new StringBuilder();

        retval.Append(RomanNumeral.NumberString(ref temp, 1000, 'M'));
        retval.Append(RomanNumeral.NumberString(ref temp, 500, 'D'));
        retval.Append(RomanNumeral.NumberString(ref temp, 100, 'C'));
        retval.Append(RomanNumeral.NumberString(ref temp, 50, 'L'));
        retval.Append(RomanNumeral.NumberString(ref temp, 10, 'X'));
        retval.Append(RomanNumeral.NumberString(ref temp, 5, 'V'));
        retval.Append(RomanNumeral.NumberString(ref temp, 1, 'I'));

        return(retval.ToString());
    }
    public static implicit operator BinaryNumeral(RomanNumeral roman)
    {
        return(new BinaryNumeral((short) roman));
    }

    public static explicit operator RomanNumeral(
    BinaryNumeral binary)
    {
        int        val = binary;
        if (val >= 1000)
            return((RomanNumeral)
                    new RomanNumeralAlternate((short) val));
        else
            return(new RomanNumeral((short) val));
    }

    private short value;
}
class BinaryNumeral
{
    public BinaryNumeral(int value)
    {
        this.value = value;
    }
    public static implicit operator BinaryNumeral(
    int value)
    {
        BinaryNumeral    retval = new BinaryNumeral(value);
        return(retval);
    }

    public static implicit operator int(
    BinaryNumeral binary)
    {
        return(binary.value);
    }

    public static implicit operator string(
    BinaryNumeral binary)
    {
        StringBuilder    retval = new StringBuilder();

        return(retval.ToString());
    }

    private int value;
}
class RomanNumeralAlternate : RomanNumeral
{
    public RomanNumeralAlternate(short value) : base(value)
    {
    }

    public static implicit operator string(
    RomanNumeralAlternate roman)
    {
        return("NYI");
    }
}
class Test
{
    public static void Main()
    {
            // Abschnitt mit impliziter Konvertierung
        RomanNumeralAlternate    roman;
        roman = new RomanNumeralAlternate(55);
        BinaryNumeral binary = roman;

        // Abschnitt mit expliziter Konvertierung
        BinaryNumeral binary2 = new BinaryNumeral(1500);
        RomanNumeralAlternate roman2;

        roman2 = (RomanNumeralAlternate) binary2;
    }
}

Der benutzerdefinierte Konvertierungsoperator gibt nun nicht RomanNumeral, sondern einen RomanNumeral-Verweis auf ein Objekt zurück und ist damit zulässig, denn es handelt sich um einen Verweis auf einen abgeleiteten Typ. Komisch vielleicht, aber zulässig. Mit der überarbeiteten Version der Konvertierungsfunktion kann die explizite Konvertierung von BinaryNumeral in RomanNumeralAlternate erfolgreich verlaufen, je nachdem, ob der RomanNumeral-Verweis ein Verweis auf ein RomanNumeral-Objekt oder ein RomanNumeralAlternate-Objekt ist.


Galileo Computing

25.5 Entwurfsrichtlinien  downtop

Beim Entwurf benutzerdefinierter Konvertierungen sollten folgende Richtlinien eingehalten werden.


Galileo Computing

25.5.1 Implizite Konvertierungen sind sichere Konvertierungen  downtop

Bei der Definition von Konvertierungen zwischen Typen sollten nur die Konvertierungen als implizit deklariert werden, bei denen weder Daten verloren gehen noch Ausnahmen ausgegeben werden.

Dies ist wichtig, da implizite Konvertierungen auftreten können, ohne dass dies offensichtlich ist.


Galileo Computing

25.5.2 Die Konvertierung im komplexeren Typ definieren  downtop

Dies bedeutet grundsätzlich nicht, einen einfachen Typ mit Konvertierungen vollzustopfen und so in einen komplexen Typ umzuwandeln. Bei Konvertierungen von und in Systemtypen muss die Konvertierung als Bestandteil der Klasse definiert werden, da die Quelle nicht verfügbar ist.

Selbst wenn die Quelle verfügbar wäre, würde es merkwürdig anmuten, die Konvertierungen von int in BinaryNumeral oder RomanNumeral in der int-Klasse zu definieren.

Gelegentlich, wie im Beispiel, sind die Klassen gleichwertig, d. h., es ist keine Klasse vorhanden, die sofort als einfacher identifiziert werden kann. In diesem Fall wählen Sie eine Klasse aus und platzieren beide Konvertierungen in der Klasse.


Galileo Computing

25.5.3 Eine Konvertierung aus oder in eine(r) Hierarchie  downtop

In meinen Beispielen fand nur eine einzige Konvertierung vom benutzerdefinierten Typ in numerische Typen und eine Konvertierung von numerischen Typen in den benutzerdefinierten Typ statt. Im Allgemeinen ist dies eine empfohlene Vorgehensweise. Anschließend sollten die integrierten Konvertierungen zur Verschiebung zwischen den Zieltypen eingesetzt werden. Bei der Auswahl des zu konvertierenden numerischen Typs sollten Sie den bevorzugen, der sich von der Größe her am ehesten eignet.

In der BinaryNumeral-Klasse beispielsweise findet eine implizite Konvertierung in einen int-Wert statt. Wenn der Benutzer einen kleineren Typ benötigt, beispielsweise short, ist dies ohne weiteres möglich.

Sind verschiedene Konvertierungen verfügbar, gelten die Überladungsregeln, und das Ergebnis ist für den Benutzer der Klasse möglicherweise nicht immer intuitiv. Dies ist besonders wichtig, wenn sowohl Typen mit als auch ohne Vorzeichen verwendet werden.


Galileo Computing

25.5.4 Konvertierungen nur bei Bedarf verwenden  downtop

Überflüssige Konvertierungen machen einem Benutzer nur das Leben schwer.


Galileo Computing

25.5.5 Konvertierungen, die in anderen Sprachen funktionieren  downtop

Einige der .NET-Sprachen unterstützen keine Konvertierungssyntax, und das Aufrufen von Konvertierungsfunktionen – die merkwürdige Namen besitzen – kann schwierig oder gar unmöglich sein. Damit Klassen auch in diesen Sprachen problemlos eingesetzt werden können, sollten Alternativversionen der Konvertierungen bereitgestellt werden. Wenn beispielsweise ein Objekt eine Konvertierung in string unterstützt, sollte auch der Aufruf von ToString() für diese Funktion möglich sein. Nachfolgend wird gezeigt, wie dies für die RomanNumeral-Klasse umgesetzt würde:

using System;
using System.Text;

class RomanNumeral
{
    public RomanNumeral(short value)
    {
        if (value > 5000)
            throw(new ArgumentOutOfRangeException());

        this.value = value;
    }
    public static explicit operator RomanNumeral(
    short value)
    {
        RomanNumeral    retval;
        retval = new RomanNumeral(value);
        return(retval);
    }

    public static implicit operator short(
    RomanNumeral roman)
    {
        return(roman.value);
    }

    static string NumberString(
    ref int value, int magnitude, char letter)
    {
        StringBuilder    numberString = new StringBuilder();

        while (value >= magnitude)
        {
            value -= magnitude;
            numberString.Append(letter);
        }
        return(numberString.ToString());
    }

    public static implicit operator string(
    RomanNumeral roman)
    {
        int        temp = roman.value;

        StringBuilder    retval = new StringBuilder();

        retval.Append(RomanNumeral.NumberString(ref temp, 1000, 'M'));
        retval.Append(RomanNumeral.NumberString(ref temp, 500, 'D'));
        retval.Append(RomanNumeral.NumberString(ref temp, 100, 'C'));
        retval.Append(RomanNumeral.NumberString(ref temp, 50, 'L'));
        retval.Append(RomanNumeral.NumberString(ref temp, 10, 'X'));
        retval.Append(RomanNumeral.NumberString(ref temp, 5, 'V'));
        retval.Append(RomanNumeral.NumberString(ref temp, 1, 'I'));

        return(retval.ToString());
    }
    public short ToShort()
    {
        return((short) this);
    }
    public override string ToString()
    {
        return((string) this);
    }

    private short value;
}

Bei der ToString()-Funktion handelt es sich um eine Außerkraftsetzung, da die ToString()-Version in object außer Kraft gesetzt wird.


Galileo Computing

25.6 So funktioniert’s  downtop

Zum Abschluss der benutzerdefinierten Konvertierungen einige Informationen dazu, wie der Compiler die etwas erklärungsbedürftigen Konvertierungen sieht. Diejenigen unter Ihnen, die wirklich an derartig langweiligen Details interessiert sind, können diese in der C#-Referenz finden.

Dieser Abschnitt kann gefahrlos übersprungen werden.


Galileo Computing

25.6.1 Konvertierungssuche  toptop

Bei der Suche nach Kandidaten für die benutzerdefinierte Konvertierung durchsucht der Compiler die Quellklasse und alle zugehörigen Basisklassen sowie die Zielklasse und deren Basisklassen.

Dies führt zu einem interessanten Fall:

public class S
{
    public static implicit operator T(S s)
{
// Hier Konvertierung
return(new T());
}
}

public class TBase
{
}

public class T: TBase
{

}
public class Test
{
    public static void Main()
    {
        S myS = new S();
        TBase tb = (TBase) myS;
    }
}

In diesem Beispiel ermittelt der Compiler die Konvertierung von S in T, und da es sich um eine explizite Konvertierung handelt, gleicht er diese mit der Konvertierung in TBase ab, die nur erfolgreich ist, wenn das von der Konvertierung zurückgegebene T tatsächlich nur ein TBase ist.

Nach einer kleinen Umstellung, dem Entfernen der Konvertierung von S und dem Hinzufügen dieser zu T, erhalten wir Folgendes:

// Fehler
class S
{
}
class TBase
{
}
class T: TBase
{
    public static implicit operator T(S s)
    {
        return(new T());
    }
}
class Test
{
    public static void Main()
    {
        S myS = new S();
        TBase tb = (TBase) myS;
    }
}

Dieser Code wird nicht kompiliert. Die Konvertierung erfolgt von S nach TBase, und der Compiler kann die Definition der Konvertierung nicht finden, da die Klasse T nicht durchsucht wird.






1    Nein, dieses struct-Element bietet weder nützliche Funktionen wie das Ersetzen von »IIII« durch »IV« noch die Konvertierung römischer Ziffern in short-Werte. Dieser Teil der Implementierung wird dem Leser als Übung überlassen.

2    Die C#-Referenz kann unter http://msdn.microsoft.com/vstudio/nextgen/technology/csharpdownload.asp heruntergeladen werden.

   

Select * from SQL Server 2000




Copyright © Galileo Press GmbH 2001 - 2002
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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de