C# ist statisch typisiert: Jede Variable, jedes Feld und jeder Ausdruck hat einen zur Kompilierzeit bekannten Typ. Die Sprache hat eine Reihe eingebauter Typen mit eigenen Schlüsselwörtern (int, double, string...). Jedes Schlüsselwort ist ein Alias für einen Typ im Namespace System, also sind int und System.Int32 derselbe Typ, ebenso string und System.String.
Die eingebauten Typen
| Typ | .NET-Typ | Größe | Wertebereich oder Genauigkeit | Literal |
|---|---|---|---|---|
bool | Boolean | 1 Byte | true oder false | true |
byte | Byte | 1 Byte | 0 bis 255 | 200 |
sbyte | SByte | 1 Byte | -128 bis 127 | -5 |
short | Int16 | 2 Byte | -32.768 bis 32.767 | 1200 |
ushort | UInt16 | 2 Byte | 0 bis 65.535 | 60000 |
int | Int32 | 4 Byte | -2.147.483.648 bis 2.147.483.647 | 42 |
uint | UInt32 | 4 Byte | 0 bis 4.294.967.295 | 42u |
long | Int64 | 8 Byte | etwa ±9,2 × 10^18 | 42L |
ulong | UInt64 | 8 Byte | 0 bis etwa 1,8 × 10^19 | 42ul |
float | Single | 4 Byte | etwa ±3,4 × 10^38, ~6 bis 9 Stellen | 2.5f |
double | Double | 8 Byte | etwa ±1,8 × 10^308, ~15 bis 17 Stellen | 2.5 oder 2.5d |
decimal | Decimal | 16 Byte | etwa ±7,9 × 10^28, 28 bis 29 Stellen | 2.5m |
char | Char | 2 Byte | eine UTF-16-Codeeinheit, U+0000 bis U+FFFF | 'A' |
string | String | Referenz | Text beliebiger Länge, unveränderlich | "hello" |
object | Object | Referenz | die Basis jedes Typs | new object() |
Ein Ganzzahl-Literal ohne Suffix ist ein int, oder der erste Typ aus uint, long und ulong, der es aufnehmen kann, wenn es für ein int zu groß ist, und ein Literal mit Dezimalpunkt ist ein double. Deshalb kompilieren float f = 2.5; und decimal d = 2.5; nicht: Das Literal ist ein double, und es gibt keine implizite Umwandlung von double in float oder decimal. Hänge das Suffix an: 2.5f, 2.5m.
Jeder numerische Typ stellt seine Grenzen als Konstanten bereit:
Ausgabe:
byte: 0 to 255
short: -32768 to 32767
int: -2147483648 to 2147483647
long: -9223372036854775808 to 9223372036854775807
decimal max: 79228162514264337593543950335
double max: 1.798E+308
sizes: int 4, long 8, double 8, char 2
255 161 3000000000
In der Praxis nutzt der meiste Code vier davon: int für Zähler und Indizes, long für IDs und große Summen, double für Messwerte und Mathematik und decimal für Geld.
decimal vs. double
double (und float) speichern Zahlen binär. Die meisten Dezimalbrüche, auch 0.1, haben keine exakte binäre Form, deshalb wird der nächstgelegene darstellbare Wert gespeichert, und kleine Fehler summieren sich. decimal speichert eine Ganzzahl zur Basis 10 plus eine Skalierung, also ist 0.1 exakt 0.1.
Ausgabe:
False
True
0.30000000000000004
0.3
double sum == 1.0: False
decimal sum == 1.0: True
Total: 59.97
Split three ways: 19.99
Nimm decimal für Geld, Preise, Steuern und alles, was ein Mensch von Hand zusammenrechnet und vergleicht. Es behält nachgestellte Nullen aus seinen Eingaben (1.50m wird als 1.50 ausgegeben) und hat 28 bis 29 signifikante Stellen.
Nimm double für physikalische Messwerte, Statistik, Geometrie und Spiele. Es ist um ein Mehrfaches schneller, braucht halb so viel Speicher, und sein Wertebereich ist riesig. Vergleiche zwei berechnete Doubles nie mit ==; vergleiche die Differenz mit einer Toleranz: Math.Abs(x - y) < 1e-9.
decimal ist keine Magie: 1m / 3m ist trotzdem 0.3333333333333333333333333333, und double-Arithmetik wirft bei Division durch null keine Exception (sie ergibt Unendlich, oder NaN bei 0.0 / 0.0), während Division durch null bei decimal und Ganzzahlen eine DivideByZeroException wirft.
Integer-Überlauf und checked
Ganzzahlarithmetik, die den Wertebereich des Typs überschreitet, läuft standardmäßig über und springt stillschweigend um. Der Compiler erkennt Überläufe in konstanten Ausdrücken, aber nicht, wenn die Werte aus Variablen kommen:
Ausgabe:
-2147483648
OverflowException
-1294967296
2147483648
checked(...) oder ein Block checked { ... } macht aus einem Überlauf eine OverflowException. Du kannst das auch für das ganze Projekt mit <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> einschalten. int x = int.MaxValue + 1; mit Konstanten zu schreiben ist ein Kompilierfehler (CS0220), weil der Compiler den Überlauf sieht.
Die letzte Zeile zeigt die Lösung für die meisten Überlauffehler: vor der Arithmetik in einen breiteren Typ umwandeln. (long)(max + 1) wäre falsch, denn die Addition ist bereits übergelaufen, wenn der Cast ausgeführt wird.
Werttypen und Referenztypen
Jeder C#-Typ ist entweder ein Werttyp oder ein Referenztyp, und dieser Unterschied entscheidet, was eine Zuweisung tut.
- Werttypen: alle numerischen Typen,
bool,char,DateTime, Enums und jedesstruct. Die Variable enthält die Daten; eine Zuweisung kopiert sie. - Referenztypen:
string, Arrays,objectund jedeclass. Die Variable enthält eine Referenz auf ein Objekt; eine Zuweisung kopiert die Referenz, sodass beide Variablen dasselbe Objekt sehen.
Ausgabe:
a=10 b=99
first[0]=99
s1=cat s2=cats
string ist ein Referenztyp, der sich in der Praxis wie ein Wert verhält: Er ist unveränderlich, und == vergleicht den Text, nicht die Referenzen. Mehr über benutzerdefinierte Werttypen unter Structs.
char und bool
Ein char ist eine UTF-16-Codeeinheit und wird in einfachen Anführungszeichen geschrieben. Darunter ist es ein numerischer Typ, es lässt sich also in int umwandeln und unterstützt Arithmetik:
Ausgabe:
66
C
True
True
7
b
True
bool hat genau zwei Werte und lässt sich nicht in Zahlen umwandeln oder aus ihnen erzeugen: if (count) ist ein Kompilierfehler, kein Test auf ungleich null. Schreibe if (count != 0). Zeichen außerhalb der Basic Multilingual Plane, etwa die meisten Emojis, belegen zwei char-Werte (ein Surrogatpaar), daher zählt die Length eines string UTF-16-Einheiten, keine sichtbaren Zeichen.
Standardwerte
Felder, Array-Elemente und default(T) bekommen den Standardwert des Typs: null für Zahlen, false für bool, '\0' für char und null für Referenztypen. Lokale Variablen bekommen keinen Standardwert und müssen vor der Verwendung zugewiesen werden.
Ausgabe:
0
0
False
True
0001-01-01
0,0,0
Seit C# 7.1 kannst du das Literal default ohne den Typ schreiben, wenn der Compiler ihn ableiten kann (int x = default;). Ein Werttyp kann nicht null sein, dafür gibt es int? (einen Nullable-Werttyp); siehe Nullable-Typen.
Ganzzahlen in Zeigergröße und beliebig große Ganzzahlen
Für Sonderfälle gibt es zwei weitere numerische Typen. nint und nuint (C# 9) sind Ganzzahlen in Zeigergröße, je nach Plattform 32 oder 64 Bit, und werden in Interop- und Low-Level-Code verwendet. System.Numerics.BigInteger nimmt Ganzzahlen beliebiger Größe auf, auf Kosten der Geschwindigkeit:
using System.Numerics;
BigInteger factorial = 1;
for (int i = 2; i <= 30; i++) factorial *= i;
// 265252859812191058636308480000000
Häufig gestellte Fragen
Welche Datentypen gibt es in C#?
Die eingebauten Typen sind die Ganzzahlen (sbyte, byte, short, ushort, int, uint, long, ulong), die Gleitkommatypen float und double, decimal für exakte Dezimalarithmetik, char für ein UTF-16-Zeichen, bool sowie die Referenztypen string und object. Jedes Schlüsselwort ist ein Alias für einen .NET-Typ: int ist System.Int32, string ist System.String.
Soll ich in C# decimal oder double für Geld verwenden?
Nimm decimal. Ein double speichert Binärbrüche, deshalb lassen sich Werte wie 0.1 nicht exakt darstellen und Summen driften ab (0.1 + 0.2 == 0.3 ist false). Ein decimal speichert Ziffern zur Basis 10, behält 28 bis 29 signifikante Stellen und liefert die Ergebnisse, die Buchhalter erwarten. double ist schneller und hat einen weit größeren Wertebereich, nimm es also für Wissenschaft, Grafik und Messwerte.
Was ist der Maximalwert von int in C#?
int.MaxValue ist 2.147.483.647 und int.MinValue ist -2.147.483.648 (eine vorzeichenbehaftete 32-Bit-Ganzzahl). Wird zu int.MaxValue 1 addiert, springt der Wert stillschweigend auf int.MinValue um, außer der Code läuft in einem checked-Kontext, wo eine OverflowException geworfen wird. Für größere Werte nimm long (bis etwa 9,2 Trillionen).
Was ist der Unterschied zwischen float und double in C#?
float hat 32 Bit mit etwa 6 bis 9 signifikanten Stellen; double hat 64 Bit mit etwa 15 bis 17. Gleitkomma-Literale sind standardmäßig double, ein float braucht also das Suffix f: float speed = 2.5f;. Nimm double, außer Speicher oder eine API (etwa Vector3 in Unity) verlangt float.
Was ist der Unterschied zwischen Werttypen und Referenztypen in C#?
Eine Variable eines Werttyps (int, double, bool, DateTime, jedes struct) enthält die Daten selbst, und eine Zuweisung kopiert sie. Eine Variable eines Referenztyps (string, Arrays, jede class) enthält eine Referenz auf ein Objekt im Heap, und eine Zuweisung kopiert die Referenz, sodass zwei Variablen auf dasselbe Objekt zeigen können.