C#
C#-Grundlagen Teil 2 – Typen in C# verstehen
C#-Grundlagen Teil 2 – Typen in C# verstehen
Im ersten Teil ging es darum, warum C# und .NET besser sind als ihr alter Ruf.
Jetzt geht es richtig los. Und zwar da, wo jede Sprache anfängt: bei den Typen.
C# ist statisch typisiert – der Compiler kennt jeden Typ und meckert vor dem Start, nicht erst in Produktion. Und C# hat ein Feature, das im Grundlagen-Kontext fast immer unterschlagen wird, obwohl es Geld spart: einen eingebauten Typ für exakte Dezimalzahlen.
Was ist überhaupt ein Typ?
Ein Typ sagt dem Compiler, was ein Wert ist und was man damit machen darf.
42 ist eine Zahl. Damit kann man rechnen.
"42" ist Text, der nur zufällig aussieht wie eine Zahl.
Zum Ausprobieren reicht ein Online-Playground wie dotnetfiddle.net – oder lokal dotnet run mit einer einzigen Datei.
Console.WriteLine(42.GetType()); // System.Int32
Console.WriteLine("42".GetType()); // System.String
Console.WriteLine(4.2.GetType()); // System.Double
Console.WriteLine(true.GetType()); // System.BooleanGetType() zeigt dir jederzeit, was ein Wert wirklich ist. Unsicher? Reinschauen. Fertig.
Die wichtigsten Basis-Typen
int – Ganzzahlen
int alter = 34;
int temperatur = -5;
int jahr = 2026;Ganze Zahlen von etwa -2,1 Milliarden bis +2,1 Milliarden. Reicht das nicht, nimmt man long.
double – Kommazahlen
double preis = 19.99;
double pi = 3.14159;Wichtig: Der Dezimaltrenner ist ein Punkt, kein Komma – auch wenn wir im Deutschen „Kommazahl“ sagen.
Doubles haben außerdem eine fiese Falle eingebaut – dazu gleich mehr, denn C# hat dafür eine Antwort, die andere Sprachen nicht haben.
decimal – die Geld-Waffe
Und hier kommt der Typ, den Umsteiger aus PHP, Java oder JavaScript nicht kennen:
decimal preis = 19.99m; // das m-Suffix macht den Unterschied
decimal summe = 0.1m + 0.2m;
Console.WriteLine(summe == 0.3m); // true (!)decimal rechnet dezimal statt binär – 0.1 + 0.2 ist damit wirklich exakt 0.3.
Dafür ist er langsamer und hat einen kleineren Wertebereich als double. Die Faustregel:
- Geld, Rechnungen, alles Kaufmännische →
decimal - Wissenschaft, Grafik, Messwerte →
double
bool – Wahr oder Falsch
bool istAngemeldet = true;
bool hatBezahlt = false;Zwei mögliche Werte: true oder false. Kein „truthy“, kein „falsy“ – if (1) kompiliert in C# nicht.
string und char – Text und Zeichen
string name = "Marcel";
char note = 'A'; // einfache Anführungszeichen, genau ein Zeichen
string begruessung = $"Hallo {name}"; // String-Interpolation mit $Das $ vor dem String aktiviert Interpolation – Ausdrücke in {...} werden eingesetzt. Verkettung mit + braucht man damit fast nie.
Gute Nachricht für alle, die aus Java kommen: == vergleicht bei Strings in C# den Inhalt. Die equals()-Falle gibt es hier nicht.
Value Types vs. Reference Types
Jetzt zum Konzept hinter dem C#-Typsystem – dem einen Unterschied, der vieles erklärt:
Value Types (int, double, bool, decimal, structs): Die Variable enthält den Wert selbst. Zuweisung kopiert.
Reference Types (string, Klassen, Arrays, Listen): Die Variable enthält einen Verweis auf das Objekt. Zuweisung kopiert nur den Verweis.
// Value Type: Kopie
int a = 1;
int b = a;
b = 2;
Console.WriteLine(a); // 1 – unverändert
// Reference Type: geteiltes Objekt
var listeA = new List<int> { 1 };
var listeB = listeA;
listeB.Add(2);
Console.WriteLine(listeA.Count); // 2 (!) – beide zeigen auf dieselbe ListeDer zweite Fall überrascht jeden Anfänger genau einmal. Danach weiß man, warum der Unterschied ein eigenes Kapitel verdient.
null und Nullable Types
null bedeutet: Hier ist bewusst kein Wert.
Value Types können von Haus aus nicht null sein – dafür gibt es das Fragezeichen:
int? alter = null; // int ODER null
decimal? rabatt = null;Und seit C# 8 gilt dasselbe Prinzip auch für Reference Types – die Nullable Reference Types:
string name = "Marcel"; // Compiler geht davon aus: nie null
string? mittlererName = null; // explizit: darf null sein
// Console.WriteLine(mittlererName.Length); // Compiler-Warnung!
Console.WriteLine(mittlererName?.Length ?? 0); // 0 – sicher?.– Zugriff nur, wenn nicht null??– Fallback-Wert, falls null
In neuen Projekten ist das standardmäßig aktiv. Damit rückt C# nah an Kotlins Null-Safety heran – als Warnung statt als harter Fehler, aber die Denkweise ist dieselbe: Wer null sein kann, muss es im Typ zugeben.
Collections – Listen und Dictionaries
var sprachen = new List<string> { "C#", "PHP", "TypeScript" };
Console.WriteLine(sprachen[0]); // C#
var user = new Dictionary<string, string>
{
["name"] = "Marcel",
["beruf"] = "Software-Entwickler",
};
Console.WriteLine(user["name"]); // MarcelFür strukturierte Daten nimmt man aber besser gleich ein Record:
public record Profil(string Name, int Alter, List<string> Hobbys);
var profil = new Profil("Marcel", 34, ["Programmieren", "Lesen", "Gaming"]);
Console.WriteLine(profil.Name); // Marcelvar und Typen in Methoden
var lässt den Compiler den Typ herleiten – statisch bleibt es trotzdem:
var alter = 34; // int, steht zur Compile-Zeit fest
var name = "Marcel"; // string
// name = 42; // CompilerfehlerUnd in Methoden sind Parameter- und Rückgabetypen Pflicht:
decimal BerechnePreisMitSteuer(decimal nettoPreis, decimal steuersatz = 0.19m)
{
return nettoPreis * (1 + steuersatz);
}
string? FindeBenutzername(int id)
{
// string? bedeutet: string ODER null
return id == 1 ? "marcel" : null;
}Was man dafür bekommt:
- Der Code dokumentiert sich selbst
- Die IDE kann einem wirklich helfen
- Falsche Aufrufe scheitern beim Kompilieren, nicht beim Kunden
Aufgaben
Genug Theorie.
Am meisten hängen bleibt, wenn man es selbst tippt. dotnetfiddle.net auf – oder lokal ein Projekt mit dotnet new console.
Aufgabe 1: Typen raten
Was gibt C# hier jeweils aus? Erst überlegen, dann ausführen.
Console.WriteLine(10 / 3);
Console.WriteLine(10 / 3.0);
Console.WriteLine("5" + 3);
Console.WriteLine(0.1 + 0.2 == 0.3);
Console.WriteLine(0.1m + 0.2m == 0.3m);Aufgabe 2: Steckbrief
Lege ein record Profil(string Name, int Alter, List<string> Hobbys, string? Beruf) an.
Erzeuge eine Instanz und gib einen Satz aus wie: Marcel ist 34 und hat 3 Hobbys.
Tipp: Hobbys.Count zählt die Einträge, $"..." baut den Satz.
Aufgabe 3: Referenzen erleben
Erzeuge eine List<int>, weise sie einer zweiten Variablen zu und füge über die zweite Variable ein Element hinzu.
Wie viele Elemente hat die erste Liste danach – und warum?
Aufgabe 4: Der Geld-Klassiker
Ein Kunde kauft drei Artikel zu je 0.10 €. Prüfe mit ==, ob die Summe 0.30 ergibt – einmal mit double, einmal mit decimal.
Was fällt auf?
Aufgabe 5: Typ-Detektiv
Schreibe eine Methode string BeschreibeTyp(object? wert), die für einen beliebigen Wert einen Satz zurückgibt, z. B. "Das ist ein Int32 mit dem Wert 42".
Tipp: wert.GetType().Name liefert den Typnamen – oder du löst es elegant mit Pattern Matching (switch-Expression). Denk an den null-Fall!
Lösungsvorschläge
Erst selbst probieren. Ehrlich. Dann vergleichen.
Lösung zu Aufgabe 1
Console.WriteLine(10 / 3); // 3 – int / int bleibt int, der Rest wird abgeschnitten
Console.WriteLine(10 / 3.0); // 3.3333333333333335 – sobald ein double dabei ist, wird double gerechnet
Console.WriteLine("5" + 3); // "53" – String + Zahl wird zur Verkettung
Console.WriteLine(0.1 + 0.2 == 0.3); // False – die klassische double-Falle
Console.WriteLine(0.1m + 0.2m == 0.3m); // True – decimal rechnet dezimal, also exaktDie letzten beiden Zeilen nebeneinander sind das ganze Argument für decimal.
Lösung zu Aufgabe 2
public record Profil(string Name, int Alter, List<string> Hobbys, string? Beruf);
var profil = new Profil(
Name: "Marcel",
Alter: 34,
Hobbys: ["Programmieren", "Lesen", "Gaming"],
Beruf: "Software-Entwickler"
);
Console.WriteLine($"{profil.Name} ist {profil.Alter} und hat {profil.Hobbys.Count} Hobbys.");Lösung zu Aufgabe 3
var listeA = new List<int> { 1 };
var listeB = listeA;
listeB.Add(2);
Console.WriteLine(listeA.Count); // 2List<T> ist ein Reference Type: listeB = listeA kopiert nur den Verweis, nicht die Liste.
Beide Variablen zeigen auf dasselbe Objekt – wer über die eine ändert, ändert auch die andere Sicht darauf.
Lösung zu Aufgabe 4
double summeDouble = 0.10 + 0.10 + 0.10;
Console.WriteLine(summeDouble == 0.30); // False – Rundungsfehler
Console.WriteLine(summeDouble); // 0.30000000000000004
decimal summeDecimal = 0.10m + 0.10m + 0.10m;
Console.WriteLine(summeDecimal == 0.30m); // True – decimal ist für genau diesen Fall gebautIn PHP oder Java lautet der Rat: Geld in Cent als Integer rechnen. In C# gibt es zusätzlich den Luxus-Weg: decimal.
Lösung zu Aufgabe 5
string BeschreibeTyp(object? wert) => wert switch
{
null => "Das ist null – hier ist bewusst kein Wert",
_ => $"Das ist ein {wert.GetType().Name} mit dem Wert {wert}",
};
Console.WriteLine(BeschreibeTyp(42)); // Das ist ein Int32 mit dem Wert 42
Console.WriteLine(BeschreibeTyp("42")); // Das ist ein String mit dem Wert 42
Console.WriteLine(BeschreibeTyp(true)); // Das ist ein Boolean mit dem Wert True
Console.WriteLine(BeschreibeTyp(null)); // Das ist null – hier ist bewusst kein WertInt32 statt int? Die C#-Schlüsselwörter sind nur Aliasse: int ist System.Int32, string ist System.String. GetType() zeigt den echten Namen.
Zusatz: Warum double überhaupt „falsch“ rechnet
Die Falle aus Aufgabe 4 ist keine C#-Eigenheit:
Console.WriteLine(0.1 + 0.2 == 0.3); // FalseDoubles sind intern binär gespeichert, und 0.1 ist binär ein unendlicher Bruch – wie 1/3 im Dezimalsystem. Das betrifft praktisch jede Sprache.
Der Unterschied: C# hat mit decimal eine eingebaute Antwort. Merksatz fürs Leben:
Kaufmännisch rechnen → decimal. Naturwissenschaftlich rechnen → double.
Wie geht es weiter?
Das waren die Bausteine, aus denen jedes C#-Programm besteht.
Im nächsten Teil geht es um Kontrollstrukturen – if, switch Expressions mit Pattern Matching, Schleifen. Also darum, wie ein Programm Entscheidungen trifft.