Menu

Nombres aléatoires en C# : Random.Next, intervalles, graines, mélange et aléatoire sécurisé

Comment générer des nombres aléatoires en C# avec la classe Random : Next et sa borne supérieure exclue, des doubles aléatoires dans un intervalle, les graines pour des résultats reproductibles, choisir un élément au hasard, mélanger une liste, le piège de new Random dans une boucle, et les nombres cryptographiquement sûrs.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

La classe System.Random génère des nombres pseudo-aléatoires : une suite déterministe qui paraît aléatoire, calculée à partir d'une graine (seed). Créez une instance et appelez ses méthodes :

Exemple de sortie :

1294270287
83
You rolled a 5
0.6060

Relancez-le et les nombres changent. La borne supérieure est exclue : Next(1, 7) peut renvoyer 1, 2, 3, 4, 5 ou 6, jamais 7. Pour un nombre entre 1 et 10 inclus, écrivez Next(1, 11). Passer un max inférieur à min lève une ArgumentOutOfRangeException.

Nombres aléatoires dans un intervalle

Next(min, max) couvre les entiers. Pour un double dans un intervalle, mettez NextDouble() à l'échelle ; pour un decimal comme un prix, construisez-le à partir d'entiers afin que le résultat ait la précision voulue :

Exemple de sortie :

23.6 C
72.42
heads
N
-3

rng.Next(100, 10000) / 100m tire un nombre entier de centimes et le divise par un decimal, donc le prix a toujours exactement deux décimales. Mettre un double à l'échelle donnerait des valeurs comme 72,4183.

Les graines : des suites reproductibles

Passer une graine entière rend la suite répétable. Deux objets Random avec la même graine produisent exactement les mêmes nombres, dans le même ordre :

Sortie :

16 29 91 99 91
16 29 91 99 91
38

Les graines sont ce qu'il vous faut pour les tests, les simulations à relancer, les replays dans les jeux et les niveaux générés procéduralement (« world seed 2026 »). Journalisez la graine utilisée, et un résultat surprenant pourra être reproduit plus tard.

Microsoft ne garantit pas qu'une graine produise la même suite sur toutes les versions de .NET. N'enregistrez pas une sortie générée par graine comme donnée (par exemple, générer des identifiants clients à partir d'une graine fixe en comptant les régénérer après une mise à jour).

Choisir un élément au hasard

Un index aléatoire s'obtient avec Next(0, count), qui est toujours un index valide puisque la borne supérieure est exclue :

Sortie :

Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common     695
rare       260
legendary  45

Le tirage pondéré fait correspondre un nombre de 0 à 99 à des intervalles dont la taille est le poids : 0 à 69 pour common, 70 à 94 pour rare, 95 à 99 pour legendary. Sur 1 000 tirages, les comptes tombent près de 700, 250 et 50 (ici 695, 260 et 45).

Mélanger une liste : Fisher-Yates

Pour mettre une liste dans un ordre aléatoire, parcourez-la depuis la fin et échangez chaque élément avec un élément aléatoire situé à sa position ou avant. C'est le mélange de Fisher-Yates ; il est rapide et tous les ordres sont également probables. Écrit comme une méthode générique, il fonctionne pour les tableaux et les listes de n'importe quel type d'élément :

Sortie :

5 A 8 6 4 7 2 3
4 2 1 3 5

Deux raccourcis qu'on trouve en ligne sont moins bons. list.OrderBy(x => rng.Next()) fonctionne mais trie, donc c'est plus lent, et les clés aléatoires peuvent entrer en collision. Échanger avec rng.Next(items.Count) au lieu de rng.Next(i + 1) semble équivalent mais rend certains ordres plus probables que d'autres. Depuis .NET 8, Random.Shared.Shuffle(array) effectue un mélange correct sur place, et rng.GetItems(choices, 5) tire cinq éléments au hasard (avec répétition).

Le piège de new Random() dans une boucle

Un bug classique consiste à créer un nouveau Random chaque fois qu'un nombre est nécessaire :

// Don't do this
int RollDie()
{
    var rng = new Random();   // new instance on every call
    return rng.Next(1, 7);
}

Sur .NET Framework, new Random() sans graine utilise l'horloge système (Environment.TickCount), qui ne change que toutes les 10 à 16 millisecondes. Les appels faits dans une boucle rapide créent des instances avec la même graine et renvoient le même nombre, si bien qu'une boucle de lancers de dés affiche 4 4 4 4 4. .NET Core et .NET 5+ donnent à chaque instance une graine tirée d'une source aléatoire partagée, ce qui masque le symptôme, mais créer un objet par nombre reste du gaspillage, et le code casse quand il est réutilisé dans des projets plus anciens ou dans Unity.

La correction : une seule instance pour la classe, stockée dans un champ :

Sortie :

3 6 6 5 6 1 2 1

La graine ne sert ici qu'à ce que l'exemple affiche la même chose à chaque exécution ; dans un vrai jeu, vous écririez new Random().

Threads et Random.Shared

Random n'est pas thread-safe. Si deux threads appellent Next sur la même instance en même temps, son état interne peut être corrompu ; le symptôme bien connu est une instance qui renvoie 0 pour toujours. Les options, de la plus simple à la moins simple :

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 est le bon choix par défaut dans du code nouveau : il est thread-safe, ne demande aucun champ et reçoit une graine aléatoire. On ne peut pas lui donner de graine, donc utilisez votre propre new Random(seed) quand vous avez besoin de reproductibilité.

Nombres aléatoires cryptographiquement sûrs

System.Random est prévisible par conception. Quiconque connaît la graine, ou voit assez de sorties, peut calculer la suite. C'est acceptable pour les jeux et l'échantillonnage, et inadapté aux mots de passe, jetons de réinitialisation, identifiants de session, clés et tirages de loterie. Pour ceux-là, utilisez RandomNumberGenerator de System.Security.Cryptography, qui lit le générateur sécurisé du système d'exploitation :

Exemple de sortie :

Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF

RandomNumberGenerator.GetInt32 (disponible sur .NET Core 3.0 et plus) renvoie un entier non biaisé dans l'intervalle. Ne construisez pas de codes sécurisés avec bytes[0] % 10 : le modulo rend certains chiffres plus probables que d'autres. Depuis .NET 6, RandomNumberGenerator.GetBytes(16) renvoie directement un nouveau tableau, et Convert.ToHexString(bytes) le formate. Pour des identifiants uniques dont le secret n'a pas d'importance, Guid.NewGuid() est plus simple.

Questions fréquentes

Comment générer un nombre aléatoire en C# ?

Créez un seul objet Random et appelez Next : var rng = new Random(); int roll = rng.Next(1, 7); donne un nombre de 1 à 6. Next(max) donne de 0 à max - 1, et NextDouble() donne un double de 0.0 inclus à 1.0 exclu. Depuis .NET 6, vous pouvez vous passer de l'objet et utiliser Random.Shared.Next(1, 7).

La borne supérieure de Random.Next est-elle incluse ?

Non. Next(min, max) renvoie un nombre supérieur ou égal à min et strictement inférieur à max. Pour un nombre de 1 à 10 inclus, écrivez Next(1, 11). C'est ce qui fait de Next(0, list.Count) un index valide dans une liste.

Pourquoi new Random() donne-t-il les mêmes nombres ?

Sur .NET Framework, new Random() prend sa graine dans l'horloge système, qui ne change que toutes les quelques millisecondes ; plusieurs instances créées dans une boucle rapide reçoivent donc la même graine et la même suite. .NET Core et .NET 5+ donnent une graine différente à chaque instance, mais la correction est la même partout : créez un seul Random et réutilisez-le.

Comment obtenir les mêmes nombres aléatoires à chaque fois en C# ?

Passez une graine au constructeur : new Random(42). Deux instances avec la même graine produisent la même suite, ce qui rend reproductibles les tests, les simulations et la génération procédurale. La suite associée à une graine peut changer d'une version de .NET à l'autre, donc ne stockez pas une sortie générée par graine comme donnée permanente.

System.Random est-il assez sûr pour des mots de passe ou des jetons ?

Non. Random est prévisible : quiconque connaît son état ou sa graine peut reproduire les nombres. Pour les mots de passe, les jetons, les clés et tout ce qui touche à la sécurité, utilisez System.Security.Cryptography.RandomNumberGenerator, par exemple RandomNumberGenerator.GetInt32(0, 10) ou RandomNumberGenerator.GetBytes(32).

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER