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.
25.1 Ein einfaches Beispiel
 
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 darzustellen1 .
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.
25.2 Prä- und Postkonvertierungen
 
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.
25.3 Konvertierungen zwischen Strukturen
 
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.
25.4 Klassen und Prä-/Postkonvertierungen
 
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.
25.5 Entwurfsrichtlinien
 
Beim Entwurf benutzerdefinierter Konvertierungen
sollten folgende Richtlinien eingehalten werden.
25.5.1 Implizite Konvertierungen sind sichere Konvertierungen
 
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.
25.5.2 Die Konvertierung im komplexeren Typ definieren
 
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.
25.5.3 Eine Konvertierung aus oder in eine(r) Hierarchie
 
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.
25.5.4 Konvertierungen nur bei Bedarf verwenden
 
Überflüssige Konvertierungen machen einem
Benutzer nur das Leben schwer.
25.5.5 Konvertierungen, die in anderen Sprachen
funktionieren
 
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.
25.6 So funktioniert’s
 
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
finden2 .
Dieser Abschnitt kann gefahrlos übersprungen
werden.
25.6.1 Konvertierungssuche
 
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.
|