Kapitel 26 Operatorüberladung
Durch die Operatorüberladung können Operatoren
für Klassen oder Strukturen definiert und mit der Operatorsyntax verwendet werden. Dies ist besonders nützlich
bei Datentypen, die gute Definitionen der speziellen Operatorbedeutung
aufweisen, sodass der Benutzer wenige Ausdrücke effizient einsetzen
kann.
Das Überladen der relationalen Operatoren (==, !=, >,
<, >=, <=) wird in Kapitel 27,
Freunde finden mit den .NET-Frameworks, im Abschnitt zum Überladen
der Equals()-Funktion der .NET-Frameworks näher behandelt.
Das Überladen von Konvertierungsoperatoren
wird in Kapitel 24, Benutzerdefinierte Konvertierung, besprochen.
26.1 Unäre Operatoren
 
Alle unären Operatoren werden als statische
Funktionen definiert, die einen einzigen Operator des Klassen- oder
Strukturtyps tragen und einen Operator dieses Typs zurückgeben.
Folgende Operatoren können überladen werden:
+ – ! ~ ++ -- true false
Die ersten sechs unären überladenen Operatoren
werden aufgerufen, wenn die entsprechende Operation für einen Typ
aufgerufen wird. Die Operatoren true und false
stehen für boolesche Typen zur Verfügung, wenn
if (a == true)
nicht äquivalent zu
if (! (a == false))
ist. Dies geschieht bei SQL-Typen, die einen Nullstatus aufweisen, der weder wahr
noch falsch ist. In diesem Fall verwendet der Compiler die überladenen
true- und false-Operatoren zum richtigen Auswerten
derartiger Anweisungen. Diese Operatoren müssen den Typ bool
zurückgeben.
Es gibt keine Möglichkeit, zwischen den im
Vorfeld und den nachträglich durchgeführten Wertezuwachs-
oder Werteabnahmeoperationen zu unterscheiden. Da es sich bei den Operatoren
um statische Funktionen und nicht um Mitgliedsfunktionen handelt, ist
diese Unterscheidung nicht wichtig.
26.2 Binäre Operatoren
 
Alle binären Operatoren tragen zwei Parameter,
von denen mindestens einer den Klassen- oder Strukturtyp aufweisen muss, in dem der Operator deklariert
wurde. Ein binärer Operator kann einen beliebigen Typ
zurückgeben, gibt jedoch typischerweise den Typ der Klasse oder
Struktur zurück, in der der Operator definiert wurde.
Folgende binäre Operatoren können überladen
werden:
+ – * / % & | ^ << >> (relationale
Operatoren)
26.3 Ein Beispiel
 
Die folgende Klasse implementiert einige der überladbaren
Operatoren:
using System;
struct RomanNumeral
{
public RomanNumeral(int value)
{
this.value = value;
}
public override string ToString()
{
return(value.ToString());
}
public static RomanNumeral operator -(RomanNumeral roman)
{
return(new RomanNumeral(-roman.value));
}
public static RomanNumeral operator +(
RomanNumeral roman1,
RomanNumeral roman2)
{
return(new RomanNumeral(
roman1.value + roman2.value));
}
public static RomanNumeral operator ++(
RomanNumeral roman)
{
return(new RomanNumeral(roman.value + 1));
}
int value;
}
class Test
{
public static void Main()
{
RomanNumeral roman1 = new RomanNumeral(12);
RomanNumeral roman2 = new RomanNumeral(125);
Console.WriteLine("Increment: {0}", roman1++);
Console.WriteLine("Addition: {0}", roman1 + roman2);
}
}
Dieser Code erzeugt die folgende Ausgabe:
Increment: 12
Addition: 138
26.4 Beschränkungen
 
Es ist nicht möglich, Operatoren für den
Mitgliedszugriff, für den Mitgliedsaufruf (Funktionsaufruf) oder die Operatoren +,
&&, ||, ?: oder new
zu überladen. Dies geschieht im Namen der Einfachheit. Obwohl mit
der Überladung lustige Dinge angestellt werden können, wird
die Verständlichkeit des Codes doch erheblich herabgesetzt, da
der Programmierer stets daran denken muss, dass der Mitgliedsaufruf
(beispielsweise) zu besonderen Operationen führen kann1 . Der Operator new kann nicht überladen werden,
da die .NET-Laufzeitumgebung für die Speicherverwaltung zuständig ist, und im C#-Dialekt bedeutet
new »Gib mir eine neue Instanz von«.
Es ist darüber hinaus nicht möglich, die
zusammengesetzten Zuweisungsoperatoren +=, *=
usw. zu überladen, da diese immer in die einfache Operation und
eine Zuordnung erweitert werden. Auf diese Weise wird vermieden, dass
nur einer der Operatoren definiert wird oder (schauder) dass die Operatoren
mit unterschiedlichen Bedeutungen definiert werden.
26.5 Richtlinien
 
Die Operatorüberladung ist eine Funktion, die
nur eingesetzt werden sollte, wenn sie erforderlich ist. Mit »erforderlich«
meine ich, dass die Verwendung zu einer Vereinfachung für den Benutzer
führt.
Gute Beispiele für die Operatorüberladung
sind beispielsweise arithmetische Operationen für komplexe Zahlen-
oder Matrizenklassen.
Ein schlechtes Beispiel ist die Definition des Zuwachsoperators (++) für eine string-Klasse
mit der Bedeutung »Erhöhe jedes Zeichen in der Zeichenfolge«.
Eine gute Richtlinie ist hierbei, nur dann einen Operator zu definieren,
wenn ein typischer Benutzer ohne Dokumentation verstehen kann, wofür
der Operator verwendet wird. Erfinden Sie keine neuen Bedeutungen für
Operatoren.
In der Praxis werden die Operatoren ==
und != am häufigsten definiert, da ansonsten unerwartete
Ergebnisse auftreten können2 .
Wenn die Typen sich wie integrierte Datentypen verhalten,
beispielsweise die BinaryNumeral-Klasse, kann es sinnvoll
sein, mehrere Operatoren zu überladen. Beim ersten Hinsehen mag
die BinaryNumeral-Klasse wie ein integer erscheinen,
der einfach von der Sytsem.Int32-Klasse abgeleitet werden
könnte und die Operatoren gratis erhält.
Das funktioniert jedoch aus verschiedenen Gründen
nicht. Zunächst können Wertetypen nicht als Basisklassen verwendet
werden, und Int32 ist ein Wertetyp. Zweitens, selbst wenn
dies möglich wäre, würde es für BinaryNumeral
nicht funktionieren, da es sich bei BinaryNumeral nicht
um eine Ganzzahl handelt, es wird lediglich ein kleiner Teil des möglichen
Wertebereichs unterstützt. Aufgrund dieser Tatsache wäre eine
Ableitung keine gute Wahl. Der kleinere Bereich bedeutet, dass selbst
bei einer Ableitung von BinaryNumeral von int
keine implizite Konvertierung von int in BinaryNumeral
vorliegt, alle Ausdrücke würden daher Typumwandlungen per
cast erfordern.
Selbst wenn dies nicht so wäre, würde
ein solches Vorgehen dennoch keinen Sinn machen, da es bei Datentypen
immer darum geht, diese möglichst klein und schlank zu halten,
d. h., eine Struktur wäre hier einer Klasse vorzuziehen. Strukturen
können natürlich nicht von anderen Objekten abgeleitet werden.
1
Man könnte natürlich damit argumentieren,
dass der Mitgliedszugriff anhand von Eigenschaften überladen werden
kann.
2
Wie bereits angesprochen, wird bei Verwendung von
== für einen Verweistyp (Klasse) ein Vergleich durchgeführt,
bei dem geprüft wird, ob die beiden verglichenen Elemente dasselbe
Objekt referenzieren – nicht, ob diese über den gleichen Inhalt
verfügen. Handelt es sich um einen Wertetyp, wird über ==
der Inhalt der Wertetypen verglichen, was ausreichend sein kann.
|