Zum Inhalt springen
  • Dunkel
  • Hell
  • System

Häufige C#-Anfängerfehler & Gotchas

Wenn dein C#-Code aus den Einsteiger-Seiten plötzlich nicht mehr tut, was du erwartest, liegt das fast immer an einer Handvoll wiederkehrender Stolpersteine. Manche werfen eine Exception und reißen die Action ab, andere scheitern komplett still: kein Fehler, keine Chat-Nachricht, einfach nichts. Diese Seite sammelt die acht häufigsten davon, jeweils mit kurzer Ursache und dem passenden Fix als Mini-Snippet. Eine Checkliste zum Durchgehen, wenn etwas hakt.

Doku: docs.streamer.bot · C# Methoden

Die Kurzfassung als Tabelle. Darunter folgt jeder Punkt einzeln mit Code.

#ProblemFix
1Direkter args["x"]-Zugriff crasht, wenn der Key fehltCPH.TryGetArg(...) mit out und Default
2Versehentliches return false stoppt die Action-KetteIm Erfolgsfall immer return true
3GetGlobalVar<int> ohne Null-Absicherung wirftGetGlobalVar<int?>(...) ?? 0
4await im synchronen Execute() kompiliert nicht.GetAwaiter().GetResult()
5new Random() pro Aufruf liefert schlechte ZufallszahlenCPH.Between(...) nutzen
6CPH.Wait(...) blockiert den ausführenden WorkerSparsam einsetzen, lange Pausen vermeiden
7Tippfehler in Variablen- oder Action-Name scheitern stillNamen in const string zentral ablegen
8Compile-Fehler landen im Log, nicht im ChatBeim Speichern aufs Compile-Ergebnis und Log achten

Der häufigste harte Crash. Du greifst per Indexer direkt auf args["rawInput"] zu, aber bei diesem Trigger gibt es das Argument gar nicht. Das wirft eine KeyNotFoundException, die Action bricht ab.

// FALSCH: crasht, wenn "rawInput" fehlt
string text = args["rawInput"].ToString();

Der Fix ist CPH.TryGetArg. Die Methode gibt einen bool zurück, ob das Argument existiert, und schreibt den Wert über den out-Parameter. So fängst du das fehlende Argument sauber ab, statt zu crashen.

// RICHTIG: prüft erst, ob das Argument da ist
if (!CPH.TryGetArg("rawInput", out string text)) {
text = "";
}

Mehr dazu auf Argumente lesen.

Der Rückgabewert von Execute() ist kein Statusflag für dich, sondern steuert die Action-Kette. Ein return false sagt Streamer.bot: brich diese Action hier ab, führe die nachfolgenden Sub-Actions nicht mehr aus. Wenn du also versehentlich aus einem else-Zweig false zurückgibst, hängt der Rest deiner Action in der Luft, ohne erkennbaren Grund.

public bool Execute() {
if (!CPH.TryGetArg("user", out string user)) {
return false; // ok hier: kein User, Kette absichtlich stoppen
}
CPH.SendMessage($"Hi {user}!");
return true; // Erfolg: Kette läuft weiter
}

Faustregel: Im Erfolgsfall immer return true. return false nur, wenn du die nachfolgenden Sub-Actions bewusst überspringen willst. Details auf return true / false.

Wenn ein Global noch nie gesetzt wurde, gibt GetGlobalVar keinen Wert zurück, sondern null. Liest du das in einen nicht-nullbaren int, scheitert das Entpacken des null-Werts und du bekommst eine NullReferenceException. Rechnest du danach weiter, crasht die Action.

// FALSCH: beim ersten Aufruf existiert der Global nicht, null entpackt nicht
int n = CPH.GetGlobalVar<int>("count", true);

Lies in einen Nullable-Typ und fang null mit dem ??-Operator ab. Nur GetGlobalVar<int?> (mit Fragezeichen) kann null sauber zurückgeben und damit den Default-Zweig auslösen.

// RICHTIG: null wird zu 0
int n = CPH.GetGlobalVar<int?>("count", true) ?? 0;

Ausführlich auf Globale Variablen.

Execute() ist eine synchrone Methode, sie gibt bool zurück, nicht Task<bool>. Deshalb kannst du darin kein await schreiben, der Code kompiliert nicht. Das betrifft jede async-API wie HttpClient mit GetStringAsync oder PostAsync.

// FALSCH: kompiliert nicht, Execute ist synchron
string json = await client.GetStringAsync("https://...");

Du überbrückst die async-Methode mit .GetAwaiter().GetResult(). Das blockiert den aktuellen Thread, bis das Ergebnis da ist, und liefert den Wert synchron zurück.

// RICHTIG: blockierend überbrücken
string json = client.GetStringAsync("https://...").GetAwaiter().GetResult();

Mehr zum HttpClient-Muster auf HttpClient & APIs.

Ein klassischer Anfängerfehler aus dem allgemeinen C#: Du legst bei jedem Execute() ein frisches new Random() an. Da der Standard-Seed auf der Systemzeit beruht und mehrere Aufrufe in derselben Millisekunde landen können, bekommst du dann mehrfach dieselbe Zahl.

// FALSCH: neuer Seed pro Aufruf, schlechte Verteilung
int roll = new Random().Next(1, 7);

In Streamer.bot brauchst du das gar nicht selbst zu lösen. CPH.Between(min, max) liefert eine Zufallszahl, und beide Grenzen sind laut Doku inklusiv. Für einen Wurf von 1 bis 6 also:

// RICHTIG: 1 bis 6, beide Grenzen inklusive
int roll = CPH.Between(1, 6);

Für eine Wahrscheinlichkeit zwischen 0.0 und 1.0 gibt es CPH.NextDouble(). Mehr dazu auf Zufall & Wartezeiten.

CPH.Wait(int milliseconds) pausiert die Ausführung, und zwar den ausführenden Worker-Thread der Queue. Solange die Action wartet, kann dieser Worker keine andere Action abarbeiten. Ein CPH.Wait(10000) legt also für zehn Sekunden alles lahm, was über dieselbe Queue läuft.

// VORSICHT: blockiert den Worker für 5 Sekunden
CPH.Wait(5000);

Kurze Pausen sind in Ordnung. Für lange Verzögerungen oder regelmäßige Aktionen sind eigene Action-Queues oder Timer die bessere Wahl. Eine Action, die du mit CPH.RunAction("Name", false) startest, läuft in ihrer eigenen Queue, und dein Code wartet nicht auf sie. Hintergründe auf Threading & Performance und Zufall & Wartezeiten.

Globals und Actions werden über exakte Strings angesprochen. Schreibst du aceCount und liest acecount, zeigt das auf einen leeren Global und du bekommst kommentarlos den Default. Genauso bei CPH.RunAction("Shoutot"): ein Tippfehler im Action-Namen, und es passiert einfach nichts, ohne Fehlermeldung.

// FALSCH: Tippfehler im Namen, scheitert still
CPH.SetGlobalVar("aceCount", 1, true);
int n = CPH.GetGlobalVar<int?>("acecount", true) ?? 0; // immer 0

Leg jeden Namen einmal in eine const string und verwende überall nur diese. Ein Tippfehler wird dann zum Compile-Fehler, statt still zu scheitern.

// RICHTIG: ein Name, eine Quelle
const string AceCountVar = "aceCount";
CPH.SetGlobalVar(AceCountVar, 1, true);
int n = CPH.GetGlobalVar<int?>(AceCountVar, true) ?? 0;

CPH.RunAction gibt zusätzlich einen bool zurück, ob die Action gefunden und ausgelöst wurde. Den kannst du prüfen und ins Log schreiben. Mehr auf Actions auslösen.

Wenn dein C#-Code einen Syntax- oder Typfehler hat, kompiliert er nicht und die Action läuft gar nicht erst an. Im Chat siehst du davon nichts: keine Nachricht, keine Reaktion. Die Compile-Fehler mit Zeilennummer stehen im Streamer.bot-Log, nicht im Twitch-Chat.

[ERROR] Compiler error (CS1002): ; expected (Zeile 12)

Beim Speichern der C#-Action zeigt Streamer.bot bereits, ob der Code erfolgreich kompiliert. Schlägt zur Laufzeit trotzdem etwas fehl, ist das Log die erste Anlaufstelle. Schreib dir mit CPH.LogInfo(...) Zwischenwerte mit, um die problematische Stelle einzugrenzen. Komplett auf Logging & Debugging.

  • args["x"] statt TryGetArg: Direkter Indexer-Zugriff crasht bei fehlendem Key. Immer CPH.TryGetArg mit out und Default nutzen.
  • return false als Gewohnheit: Im Erfolgsfall gehört return true hin, sonst stoppt die Action-Kette.
  • GetGlobalVar<int> ohne ?: Ein nicht gesetzter Global kommt als null zurück. Immer GetGlobalVar<int?>(...) ?? 0 lesen.
  • await im Execute(): Geht nicht, Execute() ist synchron. Async-Aufrufe mit .GetAwaiter().GetResult() überbrücken.
  • Eigenes new Random(): Unnötig und fehleranfällig. CPH.Between(min, max) nehmen, beide Grenzen inklusive.
  • Stille Tippfehler: Falsch geschriebene Variablen- oder Action-Namen scheitern ohne Fehler. Namen in const string zentralisieren.
  • Chat statt Log prüfen: Compile- und Laufzeitfehler stehen im Streamer.bot-Log, nie im Chat.