Menu

Números aleatorios en C#: Random.Next, rangos, semillas, mezclar y Random seguro

Cómo generar números aleatorios en C# con la clase Random: Next y su límite superior exclusivo, doubles aleatorios en un rango, semillas para obtener resultados reproducibles, elegir un elemento al azar, mezclar una lista, la trampa de new Random en un bucle, y números criptográficamente seguros.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

La clase System.Random genera números pseudoaleatorios: una secuencia determinista que parece aleatoria, a partir de una semilla. Crea una instancia y llama a sus métodos:

Salida de ejemplo:

1294270287
83
You rolled a 5
0.6060

Ejecútalo otra vez y los números cambian. El límite superior es exclusivo: Next(1, 7) puede devolver 1, 2, 3, 4, 5 o 6, nunca 7. Para un número entre 1 y 10, ambos incluidos, escribe Next(1, 11). Pasar un max menor que min lanza una ArgumentOutOfRangeException.

Números aleatorios en un rango

Next(min, max) cubre los enteros. Para un double en un rango, escala NextDouble(); para un decimal como un precio, constrúyelo a partir de enteros para que el resultado tenga la precisión que quieres:

Salida de ejemplo:

23.6 C
72.42
heads
N
-3

rng.Next(100, 10000) / 100m elige un número entero de céntimos y lo divide entre un decimal, así que el precio siempre tiene exactamente dos decimales. Escalar un double daría valores como 72.4183.

Semillas: secuencias reproducibles

Pasar una semilla entera hace que la secuencia se pueda repetir. Dos objetos Random con la misma semilla producen exactamente los mismos números, en el mismo orden:

Salida:

16 29 91 99 91
16 29 91 99 91
38

Las semillas son lo que quieres para las pruebas, las simulaciones que necesitas volver a ejecutar, las repeticiones en los juegos y los niveles generados de forma procedimental ("semilla del mundo 2026"). Registra la semilla que usaste y podrás reproducir más tarde un resultado sorprendente.

Microsoft no promete que una semilla produzca la misma secuencia en todas las versiones de .NET. No guardes como datos la salida con semilla (por ejemplo, generar ID de clientes a partir de una semilla fija y esperar volver a generarlos tras una actualización).

Elegir un elemento al azar

Un índice aleatorio es Next(0, count), que siempre es un índice válido porque el límite superior es exclusivo:

Salida:

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

El sorteo con pesos asigna un número de 0 a 99 a rangos cuyos tamaños son los pesos: de 0 a 69 es común, de 70 a 94 raro y de 95 a 99 legendario. En 1.000 sorteos, los recuentos quedan cerca de 700, 250 y 50 (aquí 695, 260 y 45).

Mezclar una lista: Fisher-Yates

Para poner una lista en orden aleatorio, recórrela desde el final e intercambia cada elemento con un elemento aleatorio situado en su posición o antes. Es la mezcla de Fisher-Yates; es rápida y todos los órdenes son igual de probables. Escrita como método genérico, funciona con arrays y listas de cualquier tipo de elemento:

Salida:

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

Dos atajos que verás por internet son peores. list.OrderBy(x => rng.Next()) funciona, pero ordena, así que es más lento, y las claves aleatorias pueden coincidir. Intercambiar con rng.Next(items.Count) en lugar de rng.Next(i + 1) parece parecido pero hace que algunos órdenes sean más probables que otros. En .NET 8 y posteriores, Random.Shared.Shuffle(array) hace una mezcla correcta en el sitio, y rng.GetItems(choices, 5) elige cinco elementos al azar (con repetición).

La trampa de new Random() en un bucle

Un bug clásico es crear un Random nuevo cada vez que se necesita un número:

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

En .NET Framework, new Random() sin semilla usa el reloj del sistema (Environment.TickCount), que solo cambia cada 10 a 16 milisegundos. Las llamadas hechas en un bucle rápido crean instancias con la misma semilla y devuelven el mismo número, así que un bucle de tiradas de dado imprime 4 4 4 4 4. .NET Core y .NET 5+ dan a cada instancia una semilla desde una fuente aleatoria compartida, lo que oculta el síntoma, pero crear un objeto por número sigue siendo un desperdicio y el código se rompe cuando se reutiliza en proyectos antiguos o en Unity.

La solución es una sola instancia para la clase, guardada en un campo:

Salida:

3 6 6 5 6 1 2 1

La semilla está aquí solo para que el ejemplo imprima lo mismo en cada ejecución; en un juego real escribirías new Random().

Hilos y Random.Shared

Random no es seguro para hilos. Si dos hilos llaman a la vez a Next sobre la misma instancia, su estado interno puede corromperse; el síntoma conocido es una instancia que a partir de ese momento devuelve 0. Las opciones, de la más sencilla a la menos:

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 es la opción por defecto correcta en el código nuevo: es seguro para hilos, no necesita campo y tiene una semilla aleatoria. No admite semilla, así que usa tu propio new Random(seed) cuando necesites reproducibilidad.

Números aleatorios criptográficamente seguros

System.Random es predecible por diseño. Cualquiera que conozca la semilla, o vea suficientes salidas, puede calcular el resto. Eso vale para juegos y muestreos, y está mal para contraseñas, tokens de restablecimiento, ID de sesión, claves y sorteos de lotería. Para esos casos, usa RandomNumberGenerator de System.Security.Cryptography, que lee del generador seguro del sistema operativo:

Salida de ejemplo:

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

RandomNumberGenerator.GetInt32 (disponible en .NET Core 3.0 y posteriores) devuelve un entero sin sesgo dentro del rango. No construyas códigos seguros con bytes[0] % 10: el módulo hace que algunos dígitos sean más probables que otros. En .NET 6 y posteriores, RandomNumberGenerator.GetBytes(16) devuelve directamente un array nuevo, y Convert.ToHexString(bytes) le da formato. Para identificadores únicos en los que el secreto no importa, Guid.NewGuid() es más sencillo.

Preguntas frecuentes

¿Cómo genero un número aleatorio en C#?

Crea un objeto Random y llama a Next: var rng = new Random(); int roll = rng.Next(1, 7); da de 1 a 6. Next(max) da de 0 a max - 1, y NextDouble() da un double desde 0,0 hasta 1,0 sin incluirlo. En .NET 6 y posteriores puedes ahorrarte el objeto y usar Random.Shared.Next(1, 7).

¿Es inclusivo el límite superior de Random.Next?

No. Next(min, max) devuelve un número mayor o igual que min y estrictamente menor que max. Para un número de 1 a 10, ambos incluidos, escribe Next(1, 11). Eso hace que Next(0, list.Count) sea un índice válido de una lista.

¿Por qué new Random() da los mismos números?

En .NET Framework, new Random() toma la semilla del reloj del sistema, que solo cambia cada pocos milisegundos, así que varias instancias creadas en un bucle rápido reciben la misma semilla y la misma secuencia. .NET Core y .NET 5+ dan a cada instancia una semilla distinta, pero la solución es la misma en todas partes: crea un solo Random y reutilízalo.

¿Cómo obtengo siempre los mismos números aleatorios en C#?

Pasa una semilla al constructor: new Random(42). Dos instancias con la misma semilla producen la misma secuencia, lo que hace reproducibles las pruebas, las simulaciones y la generación procedimental. La secuencia de una semilla puede cambiar entre versiones de .NET, así que no guardes la salida con semilla como datos permanentes.

¿Es System.Random lo bastante seguro para contraseñas o tokens?

No. Random es predecible: cualquiera que conozca su estado o su semilla puede reproducir los números. Para contraseñas, tokens, claves y todo lo relacionado con la seguridad, usa System.Security.Cryptography.RandomNumberGenerator, por ejemplo RandomNumberGenerator.GetInt32(0, 10) o RandomNumberGenerator.GetBytes(32).

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR