Die Klasse System.Random erzeugt Pseudozufallszahlen: eine deterministische Folge, die zufällig aussieht und von einem Seed ausgeht. Erzeuge eine Instanz und rufe ihre Methoden auf:
Beispielausgabe:
1294270287
83
You rolled a 5
0.6060
Führe es erneut aus, und die Zahlen ändern sich. Die Obergrenze ist exklusiv: Next(1, 7) kann 1, 2, 3, 4, 5 oder 6 zurückgeben, nie 7. Für eine Zahl zwischen 1 und einschließlich 10 schreibe Next(1, 11). Ist max kleiner als min, wird eine ArgumentOutOfRangeException geworfen.
Zufallszahlen in einem Bereich
Next(min, max) deckt Ganzzahlen ab. Für ein double in einem Bereich skaliere NextDouble(); für ein decimal wie einen Preis baue es aus Ganzzahlen, damit das Ergebnis die gewünschte Genauigkeit hat:
Beispielausgabe:
23.6 C
72.42
heads
N
-3
rng.Next(100, 10000) / 100m wählt eine ganze Zahl von Cent und teilt durch ein decimal, der Preis hat also immer genau zwei Nachkommastellen. Ein skaliertes double ergäbe Werte wie 72.4183.
Seeds: reproduzierbare Folgen
Ein ganzzahliger Seed macht die Folge wiederholbar. Zwei Random-Objekte mit demselben Seed erzeugen genau dieselben Zahlen in derselben Reihenfolge:
Ausgabe:
16 29 91 99 91
16 29 91 99 91
38
Seeds sind das Richtige für Tests, Simulationen, die du erneut ausführen musst, Replays in Spielen und prozedural generierte Level („World Seed 2026“). Protokolliere den verwendeten Seed, dann lässt sich ein überraschendes Ergebnis später reproduzieren.
Microsoft garantiert nicht, dass ein Seed in jeder .NET-Version dieselbe Folge erzeugt. Speichere geseedete Ausgaben nicht als Daten (etwa Kunden-IDs aus einem festen Seed erzeugen und erwarten, sie nach einem Upgrade neu erzeugen zu können).
Ein zufälliges Element wählen
Ein zufälliger Index ist Next(0, count), und der ist immer gültig, weil die Obergrenze exklusiv ist:
Ausgabe:
Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common 695
rare 260
legendary 45
Die gewichtete Ziehung bildet eine Zahl von 0 bis 99 auf Bereiche ab, deren Größen die Gewichte sind: 0 bis 69 ist common, 70 bis 94 rare, 95 bis 99 legendary. Über 1.000 Ziehungen landen die Zählungen nahe bei 700, 250 und 50 (hier 695, 260 und 45).
Eine Liste mischen: Fisher-Yates
Um eine Liste in zufällige Reihenfolge zu bringen, gehst du vom Ende aus und tauschst jedes Element mit einem zufälligen Element an derselben oder einer früheren Position. Das ist der Fisher-Yates-Algorithmus; er ist schnell, und jede Reihenfolge ist gleich wahrscheinlich. Als generische Methode geschrieben funktioniert er für Arrays und Listen mit beliebigem Elementtyp:
Ausgabe:
5 A 8 6 4 7 2 3
4 2 1 3 5
Zwei Abkürzungen, die du online findest, sind schlechter. list.OrderBy(x => rng.Next()) funktioniert, sortiert aber, ist also langsamer, und zufällige Schlüssel können kollidieren. Mit rng.Next(items.Count) statt rng.Next(i + 1) zu tauschen sieht ähnlich aus, macht aber manche Reihenfolgen wahrscheinlicher als andere. Ab .NET 8 mischt Random.Shared.Shuffle(array) korrekt an Ort und Stelle, und rng.GetItems(choices, 5) wählt fünf zufällige Elemente (mit Wiederholung).
Die Falle mit new Random() in einer Schleife
Ein klassischer Bug ist, jedes Mal ein neues Random zu erzeugen, wenn eine Zahl gebraucht wird:
// Don't do this
int RollDie()
{
var rng = new Random(); // new instance on every call
return rng.Next(1, 7);
}
Unter .NET Framework verwendet new Random() ohne Seed die Systemuhr (Environment.TickCount), die sich nur alle 10 bis 16 Millisekunden ändert. Schnell hintereinander in einer Schleife erzeugte Instanzen bekommen denselben Seed und liefern dieselbe Zahl, eine Schleife mit Würfelwürfen gibt also 4 4 4 4 4 aus. .NET Core und .NET 5+ seeden jede Instanz aus einer gemeinsamen Zufallsquelle, was das Symptom verbirgt, aber ein Objekt pro Zahl zu erzeugen ist trotzdem verschwenderisch, und der Code bricht, wenn er in älteren Projekten oder in Unity wiederverwendet wird.
Die Lösung ist eine Instanz für die Klasse, gespeichert in einem Feld:
Ausgabe:
3 6 6 5 6 1 2 1
Der Seed steht hier nur, damit das Beispiel bei jedem Lauf dasselbe ausgibt; in einem echten Spiel würdest du new Random() schreiben.
Threads und Random.Shared
Random ist nicht threadsicher. Rufen zwei Threads gleichzeitig Next auf derselben Instanz auf, kann ihr interner Zustand beschädigt werden; das bekannte Symptom ist eine Instanz, die ab da nur noch 0 zurückgibt. Die Optionen, von der einfachsten an:
static class Dice
{
// .NET 6 and later: a thread-safe shared instance
public static int RollShared() => Random.Shared.Next(1, 7);
// Any version: one instance per thread
[ThreadStatic] private static Random _local;
private static Random Local => _local ?? (_local = new Random(Guid.NewGuid().GetHashCode()));
public static int RollPerThread() => Local.Next(1, 7);
// Any version: a lock around one shared instance
private static readonly Random _rng = new Random();
private static readonly object _sync = new object();
public static int RollLocked()
{
lock (_sync) { return _rng.Next(1, 7); }
}
}
Random.Shared ist in neuem Code der richtige Standard: Es ist threadsicher, braucht kein Feld und wird zufällig geseedet. Es lässt sich nicht seeden, nimm also dein eigenes new Random(seed), wenn du Reproduzierbarkeit brauchst.
Kryptografisch sichere Zufallszahlen
System.Random ist absichtlich vorhersagbar. Wer den Seed kennt oder genug Ausgaben sieht, kann den Rest berechnen. Für Spiele und Stichproben ist das in Ordnung, für Passwörter, Reset-Tokens, Session-IDs, Schlüssel und Verlosungen falsch. Dafür nimmst du RandomNumberGenerator aus System.Security.Cryptography, der aus dem sicheren Generator des Betriebssystems liest:
Beispielausgabe:
Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF
RandomNumberGenerator.GetInt32 (ab .NET Core 3.0 verfügbar) gibt eine unverzerrte Ganzzahl im Bereich zurück. Baue sichere Codes nicht mit bytes[0] % 10: Der Modulo macht manche Ziffern wahrscheinlicher als andere. Ab .NET 6 gibt RandomNumberGenerator.GetBytes(16) direkt ein neues Array zurück, und Convert.ToHexString(bytes) formatiert es. Für eindeutige Bezeichner, bei denen Geheimhaltung keine Rolle spielt, ist Guid.NewGuid() einfacher.
Häufig gestellte Fragen
Wie erzeuge ich in C# eine Zufallszahl?
Erzeuge ein Random-Objekt und rufe Next auf: var rng = new Random(); int roll = rng.Next(1, 7); liefert 1 bis 6. Next(max) liefert 0 bis max - 1, und NextDouble() liefert ein double von 0.0 bis ausschließlich 1.0. Ab .NET 6 kannst du dir das Objekt sparen und Random.Shared.Next(1, 7) verwenden.
Ist die Obergrenze von Random.Next inklusive?
Nein. Next(min, max) gibt eine Zahl zurück, die größer oder gleich min und echt kleiner als max ist. Für eine Zahl von 1 bis einschließlich 10 schreibe Next(1, 11). Dadurch ist Next(0, list.Count) ein gültiger Index in eine Liste.
Warum liefert new Random() dieselben Zahlen?
Unter .NET Framework wird new Random() mit der Systemuhr geseedet, die sich nur alle paar Millisekunden ändert. Mehrere Instanzen, die schnell hintereinander in einer Schleife erzeugt werden, bekommen daher denselben Seed und dieselbe Folge. .NET Core und .NET 5+ seeden jede Instanz anders, aber die Lösung ist überall dieselbe: ein Random erzeugen und wiederverwenden.
Wie bekomme ich in C# jedes Mal dieselben Zufallszahlen?
Übergib dem Konstruktor einen Seed: new Random(42). Zwei Instanzen mit demselben Seed erzeugen dieselbe Folge, was Tests, Simulationen und prozedurale Generierung reproduzierbar macht. Die Folge für einen Seed kann sich zwischen .NET-Versionen unterscheiden, speichere geseedete Ausgaben also nicht als dauerhafte Daten.
Ist System.Random sicher genug für Passwörter oder Tokens?
Nein. Random ist vorhersagbar: Wer seinen Zustand oder Seed kennt, kann die Zahlen reproduzieren. Für Passwörter, Tokens, Schlüssel und alles Sicherheitsrelevante nimm System.Security.Cryptography.RandomNumberGenerator, zum Beispiel RandomNumberGenerator.GetInt32(0, 10) oder RandomNumberGenerator.GetBytes(32).