C# und C++ teilen sich einen Buchstaben und die Syntax mit geschweiften Klammern, aber sie arbeiten auf unterschiedlichen Ebenen. C++ (1985) kompiliert direkt in Maschinencode und lässt dich genau bestimmen, wo jedes Objekt liegt und wann es stirbt. C# (2002) kompiliert in eine Zwischensprache, die die .NET-Runtime zur Laufzeit in Maschinencode übersetzt, und ein Garbage Collector gibt Speicher für dich frei. Fast jeder Unterschied unten folgt aus dieser einen Entscheidung.
Auf einen Blick
| C# | C++ | |
|---|---|---|
| Ausführung | Verwaltet: IL, per JIT von der CLR kompiliert | Nativ: vorab in Maschinencode kompiliert |
| Speicher | Garbage Collector | Manuell, RAII, Smart Pointer |
| Zeiger | Referenzen; rohe Zeiger nur in unsafe-Code | Rohe Zeiger und Referenzen überall |
| Sicherheit | Arrays mit Grenzprüfung, keine hängenden Referenzen | Undefiniertes Verhalten bei Zugriff außerhalb der Grenzen, Use after free |
| Build-Modell | Projekte und Assemblies, keine Header | Header, Präprozessor, Übersetzungseinheiten, Linker |
| Generics | Generics, einmal geprüft, zur Laufzeit aufgelöst | Templates, zur Kompilierzeit instanziiert |
| Standardbibliothek | Groß: Collections, HTTP, JSON, Dateien, Threads | Kleiner: Container, Algorithmen, Threads |
| Kompiliergeschwindigkeit | Schnell | Langsam bei großen Codebasen |
| Spiele | Unity, Godot | Unreal, die meisten hauseigenen AAA-Engines |
| Weitere Haupteinsätze | Web-Backends, Desktop, Cloud, Tools | Engines, Browser, Betriebssysteme, Embedded, Trading |
Speicher: Garbage Collector vs. RAII
In C++ wird ein Objekt mit automatischer Speicherdauer zerstört, wenn es den Gültigkeitsbereich verlässt, und sein Destruktor läuft genau in diesem Moment. Heap-Objekte gehören Smart Pointern (std::unique_ptr, std::shared_ptr) oder werden von Hand mit new und delete verwaltet. Dieses Muster, Resource Acquisition Is Initialization (RAII), sorgt für deterministisches Aufräumen von Speicher und jeder anderen Ressource.
// C++
#include <memory>
#include <fstream>
void save() {
std::ofstream file("log.txt"); // opened here
auto buffer = std::make_unique<char[]>(4096);
file << "saved\n";
} // file closed and buffer freed here, in reverse order
In C# liegt jede Klasseninstanz auf dem verwalteten Heap, und der Garbage Collector gibt sie irgendwann später frei, sobald nichts mehr auf sie verweist. Du schreibst nie delete und kannst nie etwas freigeben, das noch benutzt wird. Speicher ist erledigt; was der GC nicht zeitnah erledigt, sind andere Ressourcen: Dateien, Sockets, Datenbankverbindungen. Dafür hat C# IDisposable und die using-Anweisung, die am Ende eines Blocks Dispose aufruft, das Nächste, was einem Destruktor entspricht:
Ausgabe:
open db
open cache
db <- SELECT 1
cache <- PING
close cache
close db
done
Der Unterschied: In C++ ist das Aufräumen für jedes lokale Objekt und jedes Objekt im Besitz eines Smart Pointers an den Gültigkeitsbereich gebunden, in C# ist es für Speicher automatisch und für alles andere optional (über using). Mehr dazu auf der Seite zur using-Anweisung.
Sicherheit: Exceptions statt undefiniertem Verhalten
In C++ über das Ende eines Arrays hinaus zu lesen ist undefiniertes Verhalten: Das Programm gibt vielleicht Unsinn aus, stürzt ab oder läuft mit beschädigtem Speicher weiter, und das Ergebnis kann sich zwischen Builds ändern. Derselbe Fehler wirft in C# eine Exception genau in dieser Zeile.
// C++: compiles, and the behavior is undefined
int scores[3] = {90, 85, 77};
int x = scores[5]; // reads whatever is in memory there
Ausgabe:
Index 5 is outside an array of length 3
name was null
Die ganze Klasse der Speicherfehler (Pufferüberläufe, Use after free, doppeltes Freigeben, hängende Zeiger) existiert in sicherem C# nicht. Das ist ein großer Teil des Grundes, warum C#-Code schneller zu schreiben und zu reviewen ist.
Zeiger und unsafe-Code
C# hat durchaus Zeiger, aber nur in unsafe-Blöcken, und das Projekt muss das mit <AllowUnsafeBlocks>true</AllowUnsafeBlocks> erlauben. Objekte auf dem verwalteten Heap können sich bei der Garbage Collection verschieben, deshalb fixierst du sie mit fixed, bevor du ihre Adresse nimmst:
// C#, requires AllowUnsafeBlocks
unsafe
{
int[] data = { 1, 2, 3 };
fixed (int* p = data)
{
*(p + 1) = 20; // data is now { 1, 20, 3 }
}
}
Unsafe-Code wird für die Interoperabilität mit nativen Bibliotheken und für einige heiße Schleifen verwendet. Heute nutzt der meiste Low-Level-Code in C# stattdessen Span<T>, ref-Locals und stackalloc, die zeigerähnliche Performance bieten und die Grenzprüfungen behalten.
Performance
C++ gibt dem Compiler das ganze Programm vorab und fügt zur Laufzeit nichts hinzu: keinen Garbage Collector, kein JIT, keine Grenzprüfungen, außer du forderst sie an. Deshalb ist es die Wahl, wo jede Mikrosekunde oder jedes Byte zählt und wo Pausen nicht akzeptabel sind.
C# bezahlt seine Sicherheit mit einer Runtime, JIT-Aufwärmen beim Start und gelegentlichen GC-Pausen. Beim Durchsatz liegt es meist nur um einen kleinen Faktor hinter C++, und der Abstand ist mit jedem .NET-Release kleiner geworden: gestuftes JIT mit profilgesteuerter Optimierung, Structs und Span<T> zur Vermeidung von Allokationen, Hardware-Intrinsics für SIMD und Native AOT, das vorab in eine einzelne native ausführbare Datei kompiliert. Bei Web-APIs, Tools und Geschäftslogik dominieren Datenbank und Netzwerk, und der Unterschied der Sprache zeigt sich selten.
Spieleentwicklung: Unity vs. Unreal
Hier begegnen die meisten der Frage. Unity wird in C# geskriptet: Gameplay-Code, UI und Tools sind C#-Klassen, die an Spielobjekten hängen, während der Engine-Kern in C++ geschrieben ist. Unreal Engine ist in C++ geschrieben, und Gameplay entsteht in C++ plus dem visuellen Skriptsystem Blueprints. Godot unterstützt sowohl GDScript als auch C#.
C# mit Unity ist schneller zu lernen und erlaubt schnellere Iteration, deshalb ist es bei Indie- und Mobile-Spielen so verbreitet. Unreal und C++ sind in AAA-Studios Standard, und Engine-Programmierer arbeiten überall in C++. Ein häufiger Weg ist, mit Unity zu beginnen und dann C++ zu lernen, wenn man zu Unreal oder zur Engine-Arbeit wechselt.
Build-Modell
Ein C++-Programm ist in Header (.h, Deklarationen) und Quelldateien (.cpp, Definitionen) aufgeteilt. Der Präprozessor fügt Header in jede Quelldatei ein, jede Datei wird separat kompiliert, und der Linker fügt die Ergebnisse zusammen. Templates werden in jeder Datei instanziiert, die sie nutzt, einer der Gründe, warum große C++-Builds langsam sind.
C# hat nichts davon. Ein Projekt ist eine Menge von .cs-Dateien, die gemeinsam zu einer Assembly (.dll) kompiliert werden; die Reihenfolge der Deklarationen und der Dateien spielt keine Rolle, und ein Typ in einer Datei kann einen Typ in einer anderen ohne Include verwenden. Bibliotheken kommen als NuGet-Pakete. Die using-Direktive importiert einen Namespace, keine Datei.
Syntaxunterschiede, die dir auffallen
- Objekte.
auto p = std::make_unique<Player>();undp->Jump();in C++;var p = new Player();undp.Jump();in C#. C# verwendet für alles.. - Strings.
std::stringist ein veränderlicher Wert;stringin C# ist ein unveränderlicher Referenztyp. - Mehrfachvererbung. C++ erlaubt einer Klasse, von mehreren Klassen zu erben; C# erlaubt eine Basisklasse plus beliebig viele Interfaces.
- Templates vs. Generics. C++-Templates sind Codegenerierung zur Kompilierzeit und erlauben Metaprogrammierung; C#-Generics werden einmal typgeprüft, mit expliziten Constraints (
where T : IComparable<T>). - Standardbibliothek. Die von C# enthält HTTP, JSON, reguläre Ausdrücke, Datei-IO, Kompression und Kryptografie; in C++ kommt vieles davon aus Bibliotheken von Drittanbietern.
Was du lernen solltest
Lerne C#, wenn du Anwendungen, Web-Backends, Tools oder Unity-Spiele bauen und schnell Ergebnisse sehen willst. Lerne C++, wenn du auf Game Engines, Grafik, Embedded-Systeme, Betriebssysteme, Browser oder alles zielst, bei dem Kontrolle auf Hardware-Ebene und vorhersagbare Latenz der Kern sind. C# ist die sanftere erste Sprache; C++ lohnt sich als zweite, weil es dir zeigt, was die C#-Runtime für dich erledigt.
Häufig gestellte Fragen
Was ist der Hauptunterschied zwischen C# und C++?
C# ist verwaltet: Es kompiliert in eine Zwischensprache, die die .NET-Runtime per JIT kompiliert, und ein Garbage Collector gibt Speicher frei. C++ kompiliert direkt in Maschinencode, und du bestimmst, wann Objekte erzeugt und zerstört werden. Dieser Tausch gibt C++ mehr Kontrolle und Vorhersagbarkeit und C# mehr Sicherheit und schnellere Entwicklung.
Ist C# einfacher als C++?
Ja, für die meisten. C# hat keine manuelle Speicherverwaltung, keine Header-Dateien, kein undefiniertes Verhalten in sicherem Code und klarere Compilerfehler. C++ ist eine größere Sprache mit mehr Möglichkeiten für subtile Fehler, etwa hängende Zeiger, Pufferüberläufe und Use after free, die der Compiler nicht erkennt.
Ist C++ schneller als C#?
Gut geschriebenes C++ ist meist schneller und vor allem vorhersagbarer, weil es keine Pausen durch den Garbage Collector und kein JIT-Aufwärmen gibt. Modernes C# verkleinert den Abstand mit Structs, Span<T>, SIMD und Ahead-of-time-Kompilierung, und bei Geschäftsanwendungen ist der Unterschied selten der Engpass. Für Engines, Treiber und Hochfrequenzhandel bleibt C++ der Standard.
Soll ich für die Spieleentwicklung C# oder C++ lernen?
Für deine ersten Spiele kommst du mit C# und Unity (oder Godot) schneller zu einem fertigen Spiel. Unreal Engine nutzt C++ (plus Blueprints), und AAA-Studios, die Engine-Code schreiben, stellen C++-Programmierer ein. Viele Entwickler beginnen mit Unity und C# und lernen C++, wenn sie auf Engine-Ebene arbeiten müssen.
Hat C# Zeiger?
Ja, innerhalb von unsafe-Codeblöcken, die mit der Projekteinstellung AllowUnsafeBlocks aktiviert werden müssen. Normales C# verwendet Referenzen, die der Garbage Collector verfolgt und die nie auf freigegebenen Speicher zeigen können. ref, Span<T> und stackalloc decken die meisten Fälle ab, in denen du zu einem Zeiger greifen würdest.