La classe System.Random genera numeri pseudo-casuali: una sequenza deterministica che sembra casuale, a partire da un seed. Crea un'istanza e chiama i suoi metodi:
Output di esempio:
1294270287
83
You rolled a 5
0.6060
Eseguilo di nuovo e i numeri cambiano. Il limite superiore è escluso: Next(1, 7) può restituire 1, 2, 3, 4, 5 o 6, mai 7. Per un numero tra 1 e 10 compresi, scrivi Next(1, 11). Passare un max minore di min lancia una ArgumentOutOfRangeException.
Numeri casuali in un intervallo
Next(min, max) copre gli interi. Per un double in un intervallo, scala NextDouble(); per un decimal come un prezzo, costruiscilo a partire da interi in modo che il risultato abbia la precisione che vuoi:
Output di esempio:
23.6 C
72.42
heads
N
-3
rng.Next(100, 10000) / 100m sceglie un numero intero di centesimi e lo divide per un decimal, quindi il prezzo ha sempre esattamente due decimali. Scalare un double darebbe valori come 72.4183.
Seed: sequenze riproducibili
Passare un seed intero rende la sequenza ripetibile. Due oggetti Random con lo stesso seed producono esattamente gli stessi numeri, nello stesso ordine:
Output:
16 29 91 99 91
16 29 91 99 91
38
I seed sono ciò che ti serve per i test, le simulazioni da rieseguire, i replay nei giochi e i livelli generati proceduralmente ("world seed 2026"). Registra il seed che hai usato, e un risultato sorprendente si potrà riprodurre in seguito.
Microsoft non garantisce che un seed produca la stessa sequenza in ogni versione di .NET. Non salvare come dati l'output generato con un seed (per esempio, generare gli ID dei clienti da un seed fisso aspettandoti di rigenerarli dopo un aggiornamento).
Scegliere un elemento a caso
Un indice casuale è Next(0, count), che è sempre un indice valido perché il limite superiore è escluso:
Output:
Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common 695
rare 260
legendary 45
L'estrazione pesata associa un numero da 0 a 99 a intervalli la cui ampiezza corrisponde ai pesi: da 0 a 69 è comune, da 70 a 94 raro, da 95 a 99 leggendario. Su 1.000 estrazioni i conteggi si avvicinano a 700, 250 e 50 (qui 695, 260 e 45).
Mescolare una lista: Fisher-Yates
Per mettere una lista in ordine casuale, percorrila dalla fine e scambia ogni elemento con un elemento casuale nella stessa posizione o prima. È lo shuffle di Fisher-Yates; è veloce e ogni ordinamento è ugualmente probabile. Scritto come metodo generico, funziona con array e liste di qualsiasi tipo di elemento:
Output:
5 A 8 6 4 7 2 3
4 2 1 3 5
Due scorciatoie che troverai online sono peggiori. list.OrderBy(x => rng.Next()) funziona ma ordina, quindi è più lento, e le chiavi casuali possono coincidere. Scambiare con rng.Next(items.Count) invece di rng.Next(i + 1) sembra simile ma rende alcuni ordini più probabili di altri. Su .NET 8 e successivi, Random.Shared.Shuffle(array) esegue uno shuffle corretto sul posto, e rng.GetItems(choices, 5) sceglie cinque elementi casuali (con ripetizione).
La trappola di new Random() in un ciclo
Un bug classico è creare un nuovo Random ogni volta che serve un numero:
// Don't do this
int RollDie()
{
var rng = new Random(); // new instance on every call
return rng.Next(1, 7);
}
Su .NET Framework, new Random() senza seed usa l'orologio di sistema (Environment.TickCount), che cambia solo ogni 10 o 16 millisecondi. Le chiamate fatte in un ciclo veloce creano istanze con lo stesso seed e restituiscono lo stesso numero, quindi un ciclo di lanci di dado stampa 4 4 4 4 4. .NET Core e .NET 5+ danno il seed a ogni istanza da una sorgente casuale condivisa, il che nasconde il sintomo, ma creare un oggetto per ogni numero resta uno spreco e il codice si rompe quando viene riusato in progetti più vecchi o in Unity.
La soluzione è una sola istanza per la classe, memorizzata in un campo:
Output:
3 6 6 5 6 1 2 1
Il seed qui serve solo perché l'esempio stampi la stessa cosa a ogni esecuzione; in un gioco vero scriveresti new Random().
Thread e Random.Shared
Random non è thread safe. Se due thread chiamano Next sulla stessa istanza nello stesso momento, il suo stato interno si può corrompere; il sintomo noto è un'istanza che da quel momento restituisce sempre 0. Le opzioni, dalla più semplice:
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 è la scelta predefinita giusta nel codice nuovo: è thread safe, non richiede un campo e riceve un seed casuale. Non si può impostare il suo seed, quindi usa il tuo new Random(seed) quando ti serve la riproducibilità.
Numeri casuali crittograficamente sicuri
System.Random è prevedibile per costruzione. Chiunque conosca il seed, o veda abbastanza output, può calcolare il resto. Va bene per giochi e campionamenti, ed è sbagliato per password, token di reset, ID di sessione, chiavi ed estrazioni a premi. Per questi usa RandomNumberGenerator di System.Security.Cryptography, che legge dal generatore sicuro del sistema operativo:
Output di esempio:
Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF
RandomNumberGenerator.GetInt32 (disponibile su .NET Core 3.0 e successivi) restituisce un intero senza distorsioni nell'intervallo. Non costruire codici sicuri con bytes[0] % 10: il modulo rende alcune cifre più probabili di altre. Su .NET 6 e successivi, RandomNumberGenerator.GetBytes(16) restituisce direttamente un nuovo array, e Convert.ToHexString(bytes) lo formatta. Per identificatori univoci dove la segretezza non conta, Guid.NewGuid() è più semplice.
Domande frequenti
Come genero un numero casuale in C#?
Crea un oggetto Random e chiama Next: var rng = new Random(); int roll = rng.Next(1, 7); dà un numero da 1 a 6. Next(max) dà un numero da 0 a max - 1, e NextDouble() dà un double da 0.0 fino a 1.0 escluso. Su .NET 6 e successivi puoi fare a meno dell'oggetto e usare Random.Shared.Next(1, 7).
Il limite superiore di Random.Next è incluso?
No. Next(min, max) restituisce un numero maggiore o uguale a min e strettamente minore di max. Per un numero da 1 a 10 compresi, scrivi Next(1, 11). Questo rende Next(0, list.Count) un indice valido per una lista.
Perché new Random() dà sempre gli stessi numeri?
Su .NET Framework, new Random() prende il seed dall'orologio di sistema, che cambia solo ogni pochi millisecondi, quindi più istanze create in un ciclo veloce ricevono lo stesso seed e la stessa sequenza. .NET Core e .NET 5+ danno a ogni istanza un seed diverso, ma la soluzione è la stessa ovunque: crea un solo Random e riusalo.
Come ottengo ogni volta gli stessi numeri casuali in C#?
Passa un seed al costruttore: new Random(42). Due istanze con lo stesso seed producono la stessa sequenza, il che rende riproducibili test, simulazioni e generazione procedurale. La sequenza per un dato seed può cambiare tra versioni di .NET, quindi non salvare l'output generato con un seed come dato permanente.
System.Random è abbastanza sicuro per password o token?
No. Random è prevedibile: chiunque ne conosca lo stato o il seed può riprodurre i numeri. Per password, token, chiavi e tutto ciò che riguarda la sicurezza usa System.Security.Cryptography.RandomNumberGenerator, per esempio RandomNumberGenerator.GetInt32(0, 10) o RandomNumberGenerator.GetBytes(32).