Kapitel 15 Operatoren
Die C#-Ausdruckssyntax basiert auf der entsprechenden Syntax von C++.
15.1 Rangfolge der Operatoren
 
Wenn ein Ausdruck mehrere Operatoren enthält,
bestimmt die Rangfolge der Operatoren die Reihenfolge, in der die Elemente
des Ausdrucks ausgewertet werden. Die Standardrangfolge kann durch das
Setzen von Klammern innerhalb einer Elementegruppe geändert werden.
int value = 1 + 2 * 3; // 1 + (2 * 3)
= 7
value = (1 + 2) * 3; // (1 + 2) * 3 = 9
In C# sind alle binären Operatoren links orientiert,
d. h., die Operationen werden von links nach rechts ausgeführt.
Eine Ausnahme bilden die Zuweisungs- und die Bedingungsoperatoren (?:), die von rechts nach links ausgeführt
werden.
In der folgenden Tabelle werden alle Operatoren
aufgeführt, die Auflistung fängt hierbei an oberster Stelle
an und nimmt hinsichtlich der Vorrangigkeit ab.
Kategorie
|
Operatoren
|
Primär
|
(x) x.y f(x) a[x] x++ x-- new typeof sizeof
checked unchecked
|
Unär
|
+ – ! ~ ++x --x (T)x
|
Multiplikativ
|
* / %
|
Additiv
|
+ -
|
Umschaltung
|
<< >>
|
Relational
|
< > <= >= is
|
Gleichwertigkeit
|
== !=
|
Logisches AND
|
&
|
Logisches XOR
|
^
|
Logisches OR
|
|
|
Bedingtes AND
|
&&
|
Bedingtes OR
|
||
|
Bedingung
|
?:
|
Zuweisung
|
= *= /= %= += -= <<= >>= &=
^= |=
|
15.2 Integrierte Operatoren
 
Bei numerischen Operationen stehen in C# für die Typen int,
uint, long, ulong, float,
double und decimal integrierte Operatoren
zur Verfügung. Da keine integrierten Operatoren für weitere
Typen vorhanden sind, müssen Ausdrücke zunächst in einen
der vorgenannten Typen konvertiert werden, bevor die Operation ausgeführt
werden kann.
Dies bedeutet, dass bei Operationen mit numerischen
Typen, die implizit in int konvertiert werden können
– die Typen, die »kleiner« sind als int
– für das Ergebnis eine Typumwandlung stattfinden muss, damit
es im gleichen Typ gespeichert werden kann.
// Fehler
class Test
{
public static void Main()
{
short s1 = 15;
short s2 = 16;
short ssum = (short) (s1 + s2); // Typumwandlung erforderlich
int i1 = 15;
int i2 = 16;
int isum = i1 + i2; // keine Typumwandlung erforderlich
}
}
15.3 Benutzerdefinierte Operatoren
 
Benutzerdefinierte Operatoren können für
Klassen deklariert werden und weisen die gleiche Funktionsweise auf
wie die integrierten Operatoren. Weitere Informationen finden Sie in
Kapitel 25, Operatorüberladung.
15.4 Numerische Umwandlungen
 
Regeln für die numerischen Umwandlungen entnehmen
Sie bitte Kapitel 15, Konvertierungen.
15.5 Arithmetische Operatoren
 
In den folgenden Abschnitten werden die arithmetischen
Operationen zusammengefasst, die in C# ausgeführt werden können.
Die Gleitkommatypen folgen sehr spezifischen Regeln1 ; detaillierte Informationen hierzu finden Sie in der C#-Sprachreferenz.
Bei Ausführung in einem geprüften Kontext können arithmetische
Ausdrücke für Nichtgleitkommatypen zu Ausnahmen führen.
15.6 Unär Plus (+)
 
Bei einem unären Plus handelt es sich bei dem
Ergebnis schlicht um den Wert des Operanden.
15.7 Unär Minus (-)
 
Das unäre Minus funktioniert nur bei Typen,
die über eine gültige negative Darstellung verfügen.
Der Rückgabewert ist der Wert des Operanden, subtrahiert von Null.
15.7.1 Addition (+)
 
In C# wird das +-Zeichen sowohl für
die Addition als auch für die Zeichenfolgenverkettung verwendet.
Numerische Addition
Die zwei Operanden werden addiert. Wird der Ausdruck
in einem geprüften Kontext ausgewertet und liegt die Summe außerhalb
des Bereichs für den Ergebnistyp, so wird die Ausnahme OverflowException
ausgegeben. Dies wird anhand des folgenden Codes veranschaulicht:
using System;
class Test
{
public static void Main()
{
byte val1 = 200;
byte val2 = 201;
byte sum = (byte) (val1 + val2); // keine Ausnahme
checked
{
byte sum2 = (byte) (val1 + val2); // Ausnahme
}
}
}
Zeichenfolgenverkettung
Zeichenfolgenverkettungen können für zwei
Zeichenfolgen oder zwischen einer Zeichenfolge und einem Operanden vom
Typ object durchgeführt werden.2 Weist einer der Operanden den Wert null auf,
wird dieser durch eine leere Zeichenfolge ersetzt.
Operanden, die nicht vom Typ string
sind, werden durch das Aufrufen der virtuellen ToString()-Methode
für das Objekt automatisch in eine Zeichenfolge konvertiert.
15.7.2 Subtraktion (-)
 
Der zweite Operand wird vom ersten Operanden subtrahiert.
Wird der Ausdruck in einem geprüften Kontext ausgewertet und liegt
die Differenz außerhalb des Bereichs für den Ergebnistyp,
so wird die Ausnahme OverflowException ausgegeben.
15.7.3 Multiplikation (*)
 
Die zwei Operanden werden multipliziert. Wird der
Ausdruck in einem geprüften Kontext ausgewertet und liegt das Ergebnis
außerhalb des Bereichs für den Ergebnistyp, so wird die Ausnahme
OverflowException ausgegeben.
15.7.4 Division (/)
 
Der erste Operand wird durch den zweiten Operanden
geteilt. Ist der zweite Operand Null, so wird die Ausnahme DivideByZero
ausgegeben.
15.7.5 Restbetrag (%)
 
Das Ergebnis x % y wird berechnet
durch x – (x / y) * y. Falls y
den Wert Null aufweist, wird die Ausnahme DivideByZero
ausgegeben.
15.7.6 Umschaltung (<< und >>)
 
Für << werden die höherwertigen
Bits verworfen und die niederwertigen leeren Bitpositionen auf Null
gesetzt.
Für >> mit uint
oder ulong werden die niederwertigen Bits verworfen und
die höherwertigen leeren Bitpositionen auf Null gesetzt.
Für >> mit int
oder long werden die niederwertigen Bits verworfen. Ist
x nicht positiv, werden die höherwertigen leeren Bitpositionen
auf Null gesetzt, ist x positiv, werden die höherwertigen
leeren Bitpositionen auf 1 gesetzt.
15.7.7 Wertezuwachs und -abname (++ und --)
 
Der Zuwachsoperator erhöht den Wert einer Variablen
um 1, der Abnahmeoperator vermindert den Wert der Variablen um 13 .
Die Operatoren für den Wertezuwachs und die
Werteabnahme können als Präfixoperator (die Variable wird
vor dem Lesen geändert) oder als Suffixoperator verwendet werden
(der Wert wird vor der Änderung zurückgegeben).
Beispiel:
int k = 5;
int value = k++; // Wert beträgt 5
value = --k; // Wert beträgt weiterhin 5
value = ++k; // Wert beträgt 6
15.8 Relationale und logische Operatoren
 
Relationale Operatoren werden für den Vergleich
zweier Werte eingesetzt, mit logischen Operatoren werden bitweise Operationen
für Werte ausgeführt.
15.8.1 Logische Negation (!)
 
Über den !-Operator kann die Negation
eines booleschen Wertes zurückgegeben werden.
15.8.2 Relationale Operatoren
 
C# definiert die folgenden relationalen Operationen:
Operation
|
Beschreibung
|
a == b
|
wahr, wenn a gleich b
|
a != b
|
wahr, wenn a ungleich b
|
a < b
|
wahr, wenn a kleiner b
|
a <= b
|
wahr, wenn a kleiner gleich b
|
a > b
|
wahr, wenn a größer
b
|
a >= b
|
wahr, wenn a größer gleich
b
|
Diese Operatoren geben ein Ergebnis vom Typ bool
zurück.
Beim Vergleich zweier Verweistypobjekte sucht der
Compiler zunächst nach relationalen Operatoren für die Objekte.
Wird kein anwendbarer Operator ermittelt und lautet die Relation ==
oder !==, wird der geeignete relationale Operator von der
Objektklasse aufgerufen. Dieser Operator prüft, ob es sich bei
den beiden Operanden um das gleiche Objekt handelt, nicht aber, ob sie
den gleichen Wert aufweisen.
Bei Wertetypen ist das Verfahren ebenso, abgesehen
davon, dass der integrierte Operator für Wertetypen jedes der Felder
im struct überprüft und ein true
zurückgibt, wenn alle Werte identisch sind.
Für den Typ string werden die
relationalen Operatoren überladen, sodass mit == und
!== die Werte der Zeichenfolgen verglichen werden, nicht
aber die Verweise.
15.8.3 Logische Operatoren
 
C# definiert die folgenden logischen Operatoren:
Operator
|
Beschreibung
|
&
|
Bitweises AND der zwei Operanden
|
|
|
Bitweises OR der zwei Operanden
|
^
|
Bitweises exklusives OR (XOR) der zwei Operanden
|
&&
|
Logisches AND der zwei Operanden
|
||
|
Logisches OR der zwei Operanden
|
Die Operatoren &, |
und ^ werden üblicherweise für integer-Datentypen
verwendet, obwohl sie auch auf bool-Typen angewendet werden
können.
Die Operatoren && und ||
unterscheiden sich von den Einzelzeichenversionen darin, dass sie eine
Umgehungsauswertung durchführen. Im Ausdruck
a && b
wird b nur ausgewertet, wenn a
wahr (true) ist. Im Ausdruck
a || b
wird b nur ausgewertet, wenn a
unwahr (false) ist.
15.8.4 Bedingungsoperator (?:)
 
Gelegentlich auch Ternär- oder Frageoperator genannt. Dieser Bedingungsoperator wählt basierend
auf einem booleschen Ausdruck aus zwei Ausdrücken aus.
int value = (x < 10) ? 15 : 5;
In diesem Beispiel wird der Steuerungsausdruck (x < 10) ausgewertet. Ist dieser
wahr, dann ist der Operatorwert der erste Ausdruck nach dem Fragezeichen,
in diesem Fall 15. Ist der Steuerungsausdruck falsch,
dann ist der Operatorwert der Ausdruck nach dem Doppelpunkt, hier 5.
15.9 Zuweisungsoperatoren
 
Zuweisungsoperatoren werden zur Zuweisung eines
Wertes zu einer Variablen eingesetzt. Es gibt zwei Formen: die einfache
Zuweisung und die komplexe Zuweisung.
15.9.1 Einfache Zuweisung
 
Die einfache Zuweisung erfolgt in C# mit dem einfachen
Gleichheitszeichen =. Damit die Zuordnung erfolgreich verläuft,
muss die rechte Seite der Zuordnung einen Typ aufweisen, der implizit
in den Variablentyp der linken Zuordnungsseite konvertiert werden kann.
15.9.2 Komplexe Zuweisung
 
Die Operatoren für die komplexe Zuweisung führen
neben der einfachen Zuordnung weitere Operationen aus. Die zugehörigen
Operatoren lauten:
+= -= *= /= %= &= |= ^= <<= >>=
Der Operator
x <op>= y
wird exakt so ausgewertet, als würde er folgendermaßen
ausgedrückt:
x = x <op> y
Hierbei gelten zwei Ausnahmen:
|
x wird nur
einmal ausgewertet, und die Auswertung wird sowohl für die Operation
als auch für die Zuweisung verwendet. |
|
Wenn x einen
Funktionsaufruf oder Arrayverweise enthält, wird es nur einmal
ausgeführt. |
Nach den normalen Konvertierungsregeln und unter
der Voraussetzung, dass x und y beides short-Ganzzahlen
sind, führt das Auswerten von
x = x + 3;
zu einem Kompilierungsfehler, da die Addition für
int-Werte ausgeführt wird, das int-Ergebnis
jedoch nicht implizit in einen short-Wert konvertiert wird.
In diesem Fall jedoch, da short implizit in int konvertiert
werden kann und folgende Schreibweise möglich ist
x = 3;
ist die Operation erlaubt.
15.10 Typenoperatoren
 
Die Typenoperatoren beschäftigen sich nicht
mit den Werten eines Objekts, sondern mit deren Typen.
15.10.1 Typeof
 
Der typeof-Operator gibt den Typ des
Objekts zurück, eine Instanz der Klasse System.Type.
Typeof ist nützlich, da so nicht erst eine Objektinstanz
erstellt werden muss, nur um das type-Objekt zu erhalten.
Ist bereits eine Instanz vorhanden, kann durch Aufruf der Funktion GetType()
für die Instanz das type-Objekt abgerufen werden.
Nachdem das type-Objekt für einen
Typ vorhanden ist, können über die Reflektion weitere Typinformationen
abgerufen werden. Weitere Informationen zu diesem Thema finden Sie in
Kapitel 31, C# im Detail, im Abschnitt »Weitergehende Reflektion«.
15.10.2 Is
 
Mit dem is-Operator wird ermittelt, ob ein Objektverweis in einen spezifischen Typ oder eine Schnittstelle
konvertiert werden kann. Sehr häufig wird mit diesem Operator ermittelt,
ob ein Objekt eine bestimmte Schnittstelle unterstützt.
using System;
interface IAnnoy
{
void PokeSister(string name);
}
class Brother: IAnnoy
{
public void PokeSister(string name)
{
Console.WriteLine("Poking {0}", name);
}
}
class BabyBrother
{
}
class Test
{
public static void AnnoyHer(string sister, params object[] annoyers)
{
foreach (object o in annoyers)
{
if (o is IAnnoy)
{
IAnnoy annoyer = (IAnnoy) o;
annoyer.PokeSister(sister);
}
}
}
public static void Main()
{
Test.AnnoyHer("Jane", new Brother(), new BabyBrother());
}
}
Dieser Code erzeugt die folgende Ausgabe:
Poking: Jane
In diesem Beispiel implementiert die Klasse Brother
die Schnittstelle IAnnoy, die Klasse BabyBrother
implementiert diese Schnittstelle nicht. Die Funktion AnnoyHer()
durchläuft alle ihr übergebenen Objekte, um zu ermitteln,
ob eines der Objekte IAnnoy unterstützt. Unterstützt
das Objekt die Schnittstelle, wird die Funktion PokeSister()
aufgerufen.
15.10.3 As
 
Der as-Operator ähnelt dem is-Operator, hier
wird jedoch nicht nur geprüft, ob es sich bei dem Objekt um einen
spezifischen Typ oder eine Schnittstelle handelt, es wird auch eine
explizite Konvertierung in diesen Typ oder die Schnittstelle durchgeführt.
Kann das Objekt nicht in diesen Typ oder die Schnittstelle konvertiert
werden, gibt der Operator null zurück. Das Verwenden
von as ist effizienter als die Verwendung von is,
da der as-Operator den Objekttyp nur einmal prüft,
beim is-Operator wird der Typ dagegen einmal bei der Operatorverwendung
und bei der Konvertierung ein zweites Mal geprüft.
Im vorangegangenen Beispiel können die Zeilen
if (o is IAnnoy)
{
IAnnoy annoyer = (IAnnoy) o;
annoyer.PokeSister(sister);
}
durch diese ersetzt werden:
IAnnoy annoyer = o as IAnnoy;
if (Annoyer != null)
annoyer.PokeSister(sister);
1
Diese Regeln entsprechen IEEE 754.
2
Da jeder Typ in ein Objekt konvertiert werden kann,
sind beliebige Typen möglich.
3
Im unsafe-Modus werden Zeiger um die Größe
des referenzierten Objekts erhöht oder erniedrigt.
|