import os BASE_DIR = "/root/uebungen_python/courses/csharp" modules_data = [ # ========================================================================= # MODUL 01: Syntax, Datentypen, Top-Level Statements & Nullables # ========================================================================= { "folder": "01_syntax_datentypen_und_top_level_statements", "title": "C# 01: Syntax, Top-Level Statements & Nullables", "track": "track_1_csharp_basics", "next_folder": "02_kontrollfluss_und_pattern_matching", "prev_folder": None, "badge": "💜 C# 12 & Modernes .NET 8", "header_title": "C# 01: Syntax, Top-Level Statements & Nullable Types", "header_desc": "Steige ein in modernes C# 12: Top-Level Statements, Value vs. Reference Types, Type Inference mit var, Nullable Reference Types und String-Interpolation.", "readme": """# C# 01: Syntax, Datentypen, Top-Level Statements & Nullables 💜 Willkommen zu **Modul 01** des C# 12 & .NET 8 Enterprise-Kurses! In diesem Modul lernst du das moderne Fundament von C# kennen: Top-Level Statements, das statische Typsystem (Value vs. Reference Types), Type Inference mit `var`, Nullable Reference Types (`#nullable enable`) sowie mächtige String-Interpolation. --- ## 💡 1. Das Wichtigste in Kürze ### Top-Level Statements in C# Seit C# 9/10 entfällt der historische Boilerplate-Code (`class Program { static void Main(string[] args) { ... } }`). Du kannst deinen Code direkt oben in der Datei schreiben: ```csharp // Modernes C# (Top-Level Statement): Console.WriteLine("Hallo .NET 8 Enterprise!"); ``` ### Wertetypen (Value Types) vs. Referenztypen (Reference Types) - **Wertetypen (Stack):** `int`, `long`, `double`, `decimal`, `bool`, `char`, `struct`. Speichern den direkten Wert. Können per Default *nicht* `null` sein (außer als `Nullable` bzw. `int?`). - **Referenztypen (Heap):** `string`, `object`, `class`, `record`, `interface`. Speichern eine Referenz (Zeiger) auf das Objekt im verwalteten Heap-Speicher. ### Nullable Reference Types (`string?`) C# schützt dich vor der gefürchteten `NullReferenceException`: - `string text = "Hallo";` $\\rightarrow$ darf niemals `null` sein! - `string? optionalerText = null;` $\\rightarrow$ signalisiert dem Compiler, dass der Wert fehlen kann. - **Null-Coalescing Operator (`??`):** `string ausgabe = optionalerText ?? "Standardwert";` - **Null-Conditional Operator (`?.`):** `int? laenge = optionalerText?.Length;` ### String Interpolation & Formatierung ```csharp string name = "Cloud-Server"; int port = 443; decimal preis = 49.99m; // Moderne Interpolation mit Formatbezeichnern: string info = $"{name}:{port} kostet {preis:F2} €"; ``` --- ## 🎯 Teilziele in `Aufgabe.cs` 1. **TODO 1:** `ErstelleServerInfo(string name, int port, double ramGb, bool isProduction)`: Liefert formatierte Server-Info: `"{name} (Port {port}) - {ramGb:F1} GB RAM [{PROD/DEV}]"`. Beispiel: `ErstelleServerInfo("AppHost", 8080, 16.0, true)` $\\rightarrow$ `"AppHost (Port 8080) - 16.0 GB RAM [PROD]"`. 2. **TODO 2:** `BerechneGesamtpreis(decimal einzelpreis, int anzahl, decimal? rabattProzent)`: Berechnet den Gesamtpreis mit Berücksichtigung eines optionalen Rabatts (`rabattProzent ?? 0m`). Formel: `einzelpreis * anzahl * (1m - (rabatt / 100m))`. Gibt `0m` zurück, wenn `anzahl <= 0` oder `einzelpreis < 0m`. 3. **TODO 3:** `FormatName(string? vorname, string? nachname, string standardName)`: Kombiniert Vorname und Nachname getrennt durch Leerzeichen. Falls beide `null`, leer oder nur Whitespace sind, wird `standardName` zurückgegeben. 4. **TODO 4:** `ParseAppPort(string? portInput, int defaultPort)`: Parst einen String in einen Integer-Port (gültig von 1 bis 65535). Bei ungültigem String, `null` oder Port außerhalb des Bereichs wird `defaultPort` geliefert. """, "aufgabe": """using System; // 🎯 C# 01: Syntax, Datentypen, Top-Level Statements & Nullables // ============================================================= public static class Aufgabe { // 🎯 TEILZIEL 1 (TODO 1): Server-Info String formatieren // Format: "{name} (Port {port}) - {ramGb:F1} GB RAM [{PROD/DEV}]" // Beispiel: ErstelleServerInfo("AppHost", 8080, 16.0, true) -> "AppHost (Port 8080) - 16.0 GB RAM [PROD]" // Beispiel: ErstelleServerInfo("TestBox", 5000, 4.25, false) -> "TestBox (Port 5000) - 4.3 GB RAM [DEV]" public static string ErstelleServerInfo(string name, int port, double ramGb, bool isProduction) { // TODO: Verwende String-Interpolation ($"...") und formatiere ramGb mit :F1 return ""; } // 🎯 TEILZIEL 2 (TODO 2): Gesamtpreis mit optionalem Rabatt berechnen // Falls rabattProzent null ist, wird 0% Rabatt angenommen (Null-Coalescing ?? nutzen). // Formel: einzelpreis * anzahl * (1m - (rabatt / 100m)) // Falls anzahl <= 0 oder einzelpreis < 0m ist, soll 0m zurückgegeben werden. public static decimal BerechneGesamtpreis(decimal einzelpreis, int anzahl, decimal? rabattProzent) { // TODO: Berechne den Rabatt und den Gesamtpreis mit decimal return 0m; } // 🎯 TEILZIEL 3 (TODO 3): Namen sicher formatieren (Null-Safety) // Setze vorname und nachname zusammen: "{vorname} {nachname}".Trim() // Falls das Ergebnis leer ist oder beide Parameter null/whitespace sind, gib standardName zurück. public static string FormatName(string? vorname, string? nachname, string standardName) { // TODO: Behandle null-Werte und liefere den formatierten Namen oder standardName return ""; } // 🎯 TEILZIEL 4 (TODO 4): Port-String sicher in Integer parsen // Versuche portInput mit int.TryParse zu parsen. // Ein Port ist gültig, wenn er zwischen 1 und 65535 (inklusive) liegt. // Bei ungültigem String, null oder außerhalb des Bereichs: gib defaultPort zurück. public static int ParseAppPort(string? portInput, int defaultPort) { // TODO: Parse und validiere den Port return 0; } } // Demo-Ausführung via Top-Level Statements: Console.WriteLine(Aufgabe.ErstelleServerInfo("K8s-Node-01", 6443, 32.0, true)); Console.WriteLine($"Preis: {Aufgabe.BerechneGesamtpreis(29.90m, 3, 10m):F2} €"); Console.WriteLine($"User: {Aufgabe.FormatName("Ada", "Lovelace", "Gast")}"); Console.WriteLine($"Port: {Aufgabe.ParseAppPort("8080", 80)}"); """, "test": """using System; using Xunit; public class TestAufgabe { // TEST: ErstelleServerInfo Produktion [Fact] public void Test_ErstelleServerInfo_Produktion() { string result = Aufgabe.ErstelleServerInfo("ProductionGateway", 443, 64.0, true); Assert.Equal("ProductionGateway (Port 443) - 64.0 GB RAM [PROD]", result); } // TEST: ErstelleServerInfo Development [Fact] public void Test_ErstelleServerInfo_Development() { string result = Aufgabe.ErstelleServerInfo("DevBox", 3000, 8.56, false); Assert.Equal("DevBox (Port 3000) - 8.6 GB RAM [DEV]", result); } // TEST: BerechneGesamtpreis ohne Rabatt [Fact] public void Test_BerechneGesamtpreis_OhneRabatt() { decimal result = Aufgabe.BerechneGesamtpreis(50m, 2, null); Assert.Equal(100m, result); } // TEST: BerechneGesamtpreis mit Rabatt [Fact] public void Test_BerechneGesamtpreis_MitRabatt() { decimal result = Aufgabe.BerechneGesamtpreis(100m, 2, 20m); Assert.Equal(160m, result); } // TEST: BerechneGesamtpreis ungueltige Werte [Fact] public void Test_BerechneGesamtpreis_UngueltigeWerte() { Assert.Equal(0m, Aufgabe.BerechneGesamtpreis(-10m, 5, 10m)); Assert.Equal(0m, Aufgabe.BerechneGesamtpreis(50m, 0, null)); Assert.Equal(0m, Aufgabe.BerechneGesamtpreis(50m, -2, 10m)); } // TEST: FormatName mit Vor- und Nachname [Fact] public void Test_FormatName_Gueltig() { Assert.Equal("Alan Turing", Aufgabe.FormatName("Alan", "Turing", "Unbekannt")); Assert.Equal("Linus", Aufgabe.FormatName("Linus", null, "Unbekannt")); Assert.Equal("Torvalds", Aufgabe.FormatName(null, "Torvalds", "Unbekannt")); } // TEST: FormatName Fallback [Fact] public void Test_FormatName_Fallback() { Assert.Equal("Gast-User", Aufgabe.FormatName(null, null, "Gast-User")); Assert.Equal("Gast-User", Aufgabe.FormatName("", " ", "Gast-User")); } // TEST: ParseAppPort gueltig [Fact] public void Test_ParseAppPort_Gueltig() { Assert.Equal(8080, Aufgabe.ParseAppPort("8080", 80)); Assert.Equal(443, Aufgabe.ParseAppPort("443", 80)); Assert.Equal(1, Aufgabe.ParseAppPort("1", 80)); Assert.Equal(65535, Aufgabe.ParseAppPort("65535", 80)); } // TEST: ParseAppPort ungueltig [Fact] public void Test_ParseAppPort_UngueltigFallback() { Assert.Equal(80, Aufgabe.ParseAppPort(null, 80)); Assert.Equal(80, Aufgabe.ParseAppPort("invalid", 80)); Assert.Equal(80, Aufgabe.ParseAppPort("0", 80)); Assert.Equal(80, Aufgabe.ParseAppPort("70000", 80)); Assert.Equal(80, Aufgabe.ParseAppPort("-5", 80)); } } """, "muster": """using System; public static class Aufgabe { public static string ErstelleServerInfo(string name, int port, double ramGb, bool isProduction) { string env = isProduction ? "PROD" : "DEV"; return $"{name} (Port {port}) - {ramGb:F1} GB RAM [{env}]"; } public static decimal BerechneGesamtpreis(decimal einzelpreis, int anzahl, decimal? rabattProzent) { if (einzelpreis < 0m || anzahl <= 0) { return 0m; } decimal rabatt = rabattProzent ?? 0m; decimal brutto = einzelpreis * anzahl; return brutto * (1m - (rabatt / 100m)); } public static string FormatName(string? vorname, string? nachname, string standardName) { string v = vorname?.Trim() ?? ""; string n = nachname?.Trim() ?? ""; string kombiniert = $"{v} {n}".Trim(); return string.IsNullOrWhiteSpace(kombiniert) ? standardName : kombiniert; } public static int ParseAppPort(string? portInput, int defaultPort) { if (int.TryParse(portInput, out int port) && port >= 1 && port <= 65535) { return port; } return defaultPort; } } """, "html_content": """

💡 1. Top-Level Statements & Modernes C# 12

In modernem C# entfällt das starre class Program { static void Main() } Gerüst. Deine Datei beginnt direkt mit ausführbarem Code:

// Modernes Top-Level Statement in C#:
string appName = "Enterprise Cloud Service";
int workerThreads = 16;
var isHealthy = true; // Typ automatisch 'bool' dank 'var'

Console.WriteLine($"Status von {appName}: Aktiv ({workerThreads} Threads, Healthy: {isHealthy})");
🏢 Analogie: Wertetypen (Stack) vs. Referenztypen (Heap)

Wertetypen (Value Types) wie int, double, decimal sind wie handschriftliche Notizzettel in deiner Hosentasche (Stack) – extrem schnell und direkt im Zugriff. Referenztypen (Reference Types) wie string oder class sind wie Schließfächer im Banktresor (Heap): Die Variable hält nur den Schlüssel (die Speicheradresse / Referenz) zum eigentlichen Objekt.

🛡️ 2. Nullable Reference Types & Null-Safety

C# verhindert den berüchtigten Billion Dollar Mistake durch strikte Null-Prüfungen zur Compile-Zeit:

C# Syntax Bedeutung Beispiel
string Non-Nullable (Darf niemals null sein) string title = "C# Mastery";
string? Nullable (Kann Text oder null enthalten) string? nickname = null;
?? (Coalescing) Fallback-Wert falls links null ist string user = nickname ?? "Gast";
?. (Conditional) Sicherer Zugriff nur wenn nicht null int? len = nickname?.Length;

💰 3. String-Interpolation & decimal für Finanzen

Für Finanz- und Währungsberechnungen in Enterprise-Anwendungen nutzt C# den präzisen 128-Bit Datentyp decimal (Suffix m):

decimal einzelpreis = 19.99m;
int anzahl = 5;
decimal? rabatt = 15m; // 15% Rabatt

decimal faktor = 1m - ((rabatt ?? 0m) / 100m);
decimal endpreis = einzelpreis * anzahl * faktor;

// Formatierung mit :F2 (zwei Dezimalstellen)
Console.WriteLine($"Endbetrag: {endpreis:F2} €");

🎯 Aufgaben & Checkliste in Aufgabe.cs

💡 .NET Pro-Tipp: String.IsNullOrWhiteSpace()

Nutze die Methode string.IsNullOrWhiteSpace(str), um gleichzeitig auf null, leere Strings "" und reine Leerzeichen " " zu prüfen!

💻 In Web-IDE öffnen → Weiter zu C# 02 →
""" } ] print("Module data defined.")