C# Números Aleatórios (Random)

Jogos jogam dados, sorteios escolhem um ganhador, quizzes embaralham as perguntas. Em todos eles o programa precisa de números que não dá para prever. No C#, quem sorteia é a classe Random ("aleatório"), e o jeito mais simples de usá-la é o Random.Shared, um sorteador pronto que todo o programa compartilha. Nesta aula você vai jogar dados, sortear alunos, embaralhar cartas, entender o que é a semente e por que, para senhas, a ferramenta é outra.

Você vai aprender
  • Como sortear um número com Random.Shared.Next, e por que o dado é Next(1, 7) e não Next(1, 6).
  • As outras formas: Next(), Next(max), NextDouble() e NextInt64.
  • Como sortear um elemento de um array, vários com GetItems, e embaralhar com Shuffle.
  • O que é a semente, e quando usar new Random(42).
  • Por que o Random não serve para senhas, e o que usar no lugar.
  • Os erros reais: ArgumentOutOfRangeException e CS1955.

Todos os programas foram rodados com o .NET 10 (dotnet --version = 10.0.112, C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs.

Os números desta aula mudam a cada execução

Esse é o objetivo: se você rodar os exemplos (no seu computador ou no editor do guia), vai ver outros números, e cada Rodar dá um resultado diferente. Os terminais da aula mostram uma execução de verdade, entre milhões possíveis. As missões conferem o que vale em qualquer sorteio (o intervalo, se não há repetição), nunca os números sorteados.


A ideia: o caderno de números

Imagine uma professora com um caderno de milhões de números escritos em sequência, sem nenhum padrão que se perceba. Quando alguém pede "um número aleatório", ela lê o próximo número do caderno.

  • Para quem recebe, os números parecem sorte.
  • Mas, se ela começar sempre da mesma página, a turma de amanhã recebe os mesmos números, na mesma ordem.
  • O número da página em que ela começa se chama semente (em inglês, seed).

O Random funciona assim. Os números não são sorte de verdade: são o resultado de uma conta, feita a partir do número anterior. Por isso se chamam pseudoaleatórios ("falsos aleatórios"). Para jogos e sorteios, isso basta. A boa notícia: o Random.Shared escolhe uma página diferente a cada vez que o programa roda, sem você fazer nada.


1. Um dado de 1 a 6

for (int i = 0; i < 10; i++)
{
    int dado = Random.Shared.Next(1, 7);
    Console.Write($"{dado} ");
}
Console.WriteLine();
Random
A classe que sorteia. Ela vem do System, que todo programa já enxerga.
.Shared
"Compartilhado": um sorteador pronto, criado pelo próprio .NET, que qualquer parte do programa pode usar. Ele já vem com uma semente diferente a cada execução. .NET 6
.Next(1, 7)
"Próximo": sorteia o próximo número inteiro, de 1 até 6. O primeiro número (1) entra; o segundo (7) não entra. É a mesma regra do intervalo 1..7 da aula Índices e Intervalos.
$ dotnet run dado.cs
4 3 4 5 4 3 4 6 2 4
$ dotnet run dado.cs
1 2 5 1 5 6 3 5 3 3      « outra partida, outros números
Por que o 7, e não o 6?

O segundo número do Next é o limite, que fica de fora. Por isso, para um dado, é Next(1, 7). Parece estranho, mas tem uma vantagem: Next(0, n) dá exatamente os índices de um array de n elementos (de 0 a n − 1), e 7 − 1 = 6 é a quantidade de resultados possíveis. Escrever Next(1, 6) é o erro mais comum desta aula: o 6 nunca sai.

Missão: o programa joga o dado 10 mil vezes e mostra o menor e o maior resultado. Rode e veja: o maior é 5, porque o 6 nunca sai. Conserte o Next para o dado dar de 1 a 6 (a saída tem de ter Menor: 1 e Maior: 6).

Menor: 1
Maior: 6
int menor = 100, maior = 0;
for (int i = 0; i < 10_000; i++)
{
    int dado = Random.Shared.Next(1, 6);   // conserte: tem de dar de 1 a 6
    menor = Math.Min(menor, dado);
    maior = Math.Max(maior, dado);
}
Console.WriteLine($"Menor: {menor}");
Console.WriteLine($"Maior: {maior}");
int menor = 100, maior = 0;
for (int i = 0; i < 10_000; i++)
{
    int dado = Random.Shared.Next(1, 7);
    menor = Math.Min(menor, dado);
    maior = Math.Max(maior, dado);
}
Console.WriteLine($"Menor: {menor}");
Console.WriteLine($"Maior: {maior}");

2. As formas de sortear

Random r = Random.Shared;
Console.WriteLine(r.Next());
Console.WriteLine(r.Next(10));
Console.WriteLine(r.Next(1, 101));
Console.WriteLine(r.NextDouble());
Console.WriteLine(r.NextInt64(1, 10_000_000_000));
$ dotnet run tipos.cs
1011742441
6
4
0,6050410577727472
6739814538
FormaSorteiaExemplo de uso
Next()um int de 0 até 2.147.483.646Raramente: um número grande qualquer
Next(max)de 0 até max − 1Next(10): um algarismo de 0 a 9; Next(v.Length): um índice do array v
Next(min, max)de min até max − 1Next(1, 7): um dado; Next(1, 101): de 1 a 100
NextDouble()um double de 0 (entra) até 1 (não entra)Chances: if (r.NextDouble() < 0.3) acontece em 30% das vezes
NextInt64(min, max)um long, para intervalos maiores que o intUm número de conta de 10 dígitos
Random r = Random.Shared;
Uma variável com um nome curto para o mesmo sorteador, só para não repetir Random.Shared em toda linha.
10_000_000_000
Os _ só separam os milhares para quem lê (aula Inteiros): é dez bilhões.

Um número com vírgula num intervalo

O NextDouble() dá de 0 a 1. Para outro intervalo, multiplique pelo tamanho e some o começo. Uma temperatura entre 20 e 30 graus:

double temperatura = 20 + Random.Shared.NextDouble() * 10;
Console.WriteLine($"{temperatura:F1} °C");
$ dotnet run faixa.cs
29,3 °C
$ dotnet run faixa.cs
21,0 °C

O NextDouble() * 10 dá de 0 a 10; somando 20, de 20 a 30. O :F1 mostra uma casa decimal (aula Formatar Números).


3. Sortear e embaralhar listas

Sortear um elemento

A professora Ana quer sortear quem apresenta o trabalho. O truque é sortear um índice: Next(alunos.Length) dá de 0 até Length − 1, exatamente as posições do array (aula Arrays).

string[] alunos = ["Ana", "Bia", "Caio", "Davi", "Eva"];
string escolhido = alunos[Random.Shared.Next(alunos.Length)];
Console.WriteLine($"Quem apresenta hoje: {escolhido}");
string[] dois = Random.Shared.GetItems(alunos, 2);
Console.WriteLine($"Com repetição: {string.Join(", ", dois)}");
$ dotnet run sortear.cs
Quem apresenta hoje: Eva
Com repetição: Eva, Ana
alunos[Random.Shared.Next(alunos.Length)]
Lê-se de dentro para fora: sorteia um número de 0 a 4, e usa como índice. Nunca passa do fim, porque o limite (Length) fica de fora.
Random.Shared.GetItems(alunos, 2)
"Pegar itens": sorteia 2 elementos, com repetição: o mesmo aluno pode sair duas vezes, como num dado jogado duas vezes. .NET 8

Missão: o programa sempre escolhe o primeiro aluno, alunos[0]. Troque o 0 por um índice sorteado com Random.Shared.Next(alunos.Length), para cada Rodar escolher um aluno qualquer da lista. Rode algumas vezes e veja o nome mudar.

string[] alunos = ["Ana", "Bia", "Caio", "Davi", "Eva"];
string escolhido = alunos[0];   // sorteie o índice
Console.WriteLine($"Quem apresenta hoje: {escolhido}");
string[] alunos = ["Ana", "Bia", "Caio", "Davi", "Eva"];
string escolhido = alunos[Random.Shared.Next(alunos.Length)];
Console.WriteLine($"Quem apresenta hoje: {escolhido}");

Embaralhar

int[] cartas = [1, 2, 3, 4, 5, 6, 7, 8];
Random.Shared.Shuffle(cartas);
Console.WriteLine(string.Join(" ", cartas));
$ dotnet run embaralhar.cs
7 3 8 4 5 6 2 1

O Shuffle ("embaralhar") .NET 8 troca os elementos de lugar no próprio array, como o Array.Sort da aula A Classe Array, e com a mesma pegadinha: a ordem antiga some. Todas as cartas continuam lá, cada uma uma vez só. Por isso, para sortear vários sem repetição (os números de uma loteria, a ordem das apresentações), o jeito é embaralhar e pegar os primeiros: cartas[..3].


4. A semente: new Random(42)

Às vezes você quer os mesmos números toda vez: para testar um jogo (e reproduzir o bug que só aparece quando sai 6, 6, 1), ou para gerar sempre o mesmo mapa a partir de um "código do mundo". Para isso, crie o seu próprio sorteador com uma semente fixa:

Random sorteio = new Random(42);
for (int i = 0; i < 10; i++)
{
    Console.Write($"{sorteio.Next(1, 7)} ");
}
Console.WriteLine();
Console.WriteLine(sorteio.Next());
Console.WriteLine(sorteio.NextDouble());
Console.WriteLine(sorteio.Next(100));
$ dotnet run semente.cs
5 1 1 4 2 2 5 4 2 5
503774878
0,2573213978937461
50
$ dotnet run semente.cs
5 1 1 4 2 2 5 4 2 5      « exatamente os mesmos, na mesma ordem
503774878
0,2573213978937461
50
new Random(42)
Cria um sorteador novo (new, aula Classes e Objetos), começando na "página" 42 do caderno. Qualquer número inteiro serve de semente; 42 é só um costume dos programadores.
sorteio.Next(1, 7)
Os mesmos métodos do Random.Shared, mas agora chamados na sua variável.

Rodou duas vezes e saíram os mesmos números. No editor do guia, o mesmo programa dá a mesma sequência (5 1 1 4 2 2 5 4 2 5, 503774878…): o guia conferiu, rodando o programa no editor e no dotnet do Linux, e também com Shuffle e GetItems. Mesmo assim, as missões não dependem deles, porque a documentação do .NET avisa que a conta por trás do Random pode mudar numa versão futura. Use a semente para testar e repetir, não como uma promessa eterna.

FormaSementeQuando usar
Random.SharedDiferente a cada execução, escolhida pelo .NETQuase sempre: jogos, sorteios, testes de "e se"
new Random(42)Fixa: a mesma sequência toda vezTestes que precisam se repetir, mapas a partir de um código
new Random()Diferente a cada vezCódigo antigo, de antes do .NET 6. Hoje, prefira o Random.Shared
E o new Random() dentro do laço?

Você vai ler em tutoriais antigos que criar um new Random() a cada volta do laço repete o mesmo número. Isso acontecia no .NET Framework, que usava o relógio como semente. No .NET moderno, cada new Random() começa num ponto diferente, e o problema sumiu. Mas um new Random(42) dentro do laço repete, sim: cada volta recomeça da página 42.

$ cat laco-new.cs
for (int i = 0; i < 5; i++)
{
    Random r = new Random();
    Console.Write($"{r.Next(1, 7)} ");
}
Console.WriteLine();
for (int i = 0; i < 5; i++)
{
    Random r = new Random(42);
    Console.Write($"{r.Next(1, 7)} ");
}
Console.WriteLine();
$ dotnet run laco-new.cs
2 1 1 6 3
5 5 5 5 5         « semente fixa recriada a cada volta: sempre o primeiro número

A regra simples: um sorteador só, criado uma vez, fora do laço. Com o Random.Shared, nem precisa pensar nisso.


5. Para senhas: RandomNumberGenerator

A sequência do Random sai de uma conta. Para um jogo, ninguém consegue (nem quer) adivinhar o próximo número. Mas, para uma senha, um código de verificação ou um sorteio com dinheiro envolvido, alguém pode querer, e há técnicas para isso. Nesses casos, o .NET tem o RandomNumberGenerator ("gerador de números aleatórios"), que usa o sorteador criptográfico do sistema operacional, feito para ser imprevisível:

using System.Security.Cryptography;
string letras = "abcdefghijkmnpqrstuvwxyz23456789";
char[] senha = new char[10];
for (int i = 0; i < senha.Length; i++)
{
    senha[i] = letras[RandomNumberGenerator.GetInt32(letras.Length)];
}
Console.WriteLine(new string(senha));
Console.WriteLine(RandomNumberGenerator.GetString(letras, 10));
$ dotnet run senha.cs
4vyzbhnuxc
ufypp5sa3m
using System.Security.Cryptography;
"Segurança, criptografia": onde mora o RandomNumberGenerator. Vai na primeira linha do arquivo (aula Namespaces e using).
letras
As letras e algarismos que podem sair. Faltam o l, o o, o 0 e o 1 de propósito: são fáceis de confundir quando alguém copia a senha.
RandomNumberGenerator.GetInt32(letras.Length)
Como o Next(max): de 0 até Length − 1, só que imprevisível. Não tem semente: não dá para repetir.
RandomNumberGenerator.GetString(letras, 10)
O laço inteiro numa linha: 10 caracteres sorteados do texto letras. .NET 8
Regra do guia

Random (e Random.Shared) para jogos e sorteios do dia a dia. RandomNumberGenerator para tudo o que protege alguma coisa: senhas, tokens, códigos de confirmação.


Quando dá errado

O 6 nunca sai: Next(1, 6)

O que significa: o segundo número do Next fica de fora. Next(1, 6) dá de 1 a 5. Não há erro nem aviso: o dado só é "viciado".

Como consertar: o limite é o maior valor mais 1: Next(1, 7). Para um índice de array, Next(v.Length).

Unhandled exception. System.ArgumentOutOfRangeException: 'minValue' cannot be greater than maxValue.
$ cat invertido.cs
int n = Random.Shared.Next(6, 1);
$ dotnet run invertido.cs
Unhandled exception. System.ArgumentOutOfRangeException: 'minValue' cannot be greater than maxValue. (Parameter 'minValue')
   at System.Random.ThrowMinMaxValueSwapped()
   at System.Random.ThreadSafeRandom.Next(Int32 minValue, Int32 maxValue)
   at Program.<Main>$(String[] args) in /home/ana/cs/invertido.cs:line 1

O que significa: "o mínimo não pode ser maior que o máximo". Os dois números estão trocados. O programa terminou com o código 134. O mesmo acontece com Next(-5) (limite negativo), e o Next(0) não dá erro mas devolve sempre 0, e um alunos[Random.Shared.Next(alunos.Length)] com o array vazio quebra com IndexOutOfRangeException, porque não há ninguém para sortear.

Como consertar: primeiro o menor, depois o maior: Next(1, 6). Se os valores vêm de fora, confira antes, ou use Math.Min e Math.Max (aula Matemática).

error CS1955: O membro não invocável "Random.Shared" não pode ser usado como um método.
$ cat shared-semente.cs
Random r = Random.Shared(42);
$ dotnet run shared-semente.cs
/home/ana/cs/shared-semente.cs(1,19): error CS1955: O membro não invocável "Random.Shared" não pode ser usado como um método.

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: o Random.Shared é um sorteador pronto, não uma "tecla" que se aperta com parênteses. E ele não aceita semente: a dele é escolhida pelo .NET. Em inglês: Non-invocable member 'Random.Shared' cannot be used like a method.

Como consertar: para uma semente fixa, crie o seu: Random r = new Random(42);. Sem semente, use Random.Shared sem parênteses.

Sempre o mesmo número

O que significa: há uma semente fixa (new Random(42)) que você esqueceu lá, ou ela está sendo criada de novo a cada volta do laço.

Como consertar: use Random.Shared, ou crie o sorteador uma vez, antes do laço.


Referência rápida

EscritaO que faz
Random.Shared.Next(1, 7)Um inteiro de 1 a 6 (o 7 fica de fora)
Random.Shared.Next(n)De 0 a n − 1
Random.Shared.NextDouble()Um double de 0 até antes de 1
min + Random.Shared.NextDouble() * (max - min)Um double entre min e max
Random.Shared.NextInt64(min, max)Um long de min a max − 1
v[Random.Shared.Next(v.Length)]Um elemento sorteado do array
Random.Shared.GetItems(v, n)n elementos sorteados, com repetição
Random.Shared.Shuffle(v)Embaralha o próprio array
new Random(42)Sorteador com semente fixa: a mesma sequência toda vez
RandomNumberGenerator.GetInt32(n)De 0 a n − 1, imprevisível (senhas)
RandomNumberGenerator.GetString(letras, n)Texto de n caracteres sorteados de letras

Exercícios

1. De 1 a 100

Complete para sortear um número de 1 a 100:

int n = Random.Shared.Next(1, );

2. Um índice

Complete para sortear uma posição válida do array cartas:

int i = Random.Shared.Next();

3. Semente fixa

Complete para criar um sorteador que dá sempre a mesma sequência, com a semente 7:

Random r = ;

4. Quais valores Random.Shared.Next(1, 6) pode devolver?

5. Para gerar o código de recuperação de senha de um site, a Ana deve usar…

6. Um programa com new Random(42) foi rodado três vezes. O que aconteceu com os números?

Missão final: o jogo da loteria

Missão: faça um jogo de loteria: 6 números diferentes, de 1 a 60. As duas últimas linhas do programa conferem o seu jogo; não mude essas linhas. Dica: sortear 6 vezes com Next pode repetir um número. Encha um array com os números de 1 a 60, embaralhe com Shuffle e pegue os 6 primeiros ([..6]). Os números do seu jogo serão outros a cada Rodar; as duas conferências têm de dar True.

Jogo: 4 17 23 38 51 60
6 números diferentes: True
Todos entre 1 e 60: True
int[] jogo = new int[6];

// sorteie aqui os 6 números

Array.Sort(jogo);
Console.WriteLine($"Jogo: {string.Join(" ", jogo)}");
Console.WriteLine($"6 números diferentes: {jogo.Distinct().Count() == 6}");
Console.WriteLine($"Todos entre 1 e 60: {Array.TrueForAll(jogo, n => n >= 1 && n <= 60)}");
int[] numeros = new int[60];
for (int i = 0; i < numeros.Length; i++)
{
    numeros[i] = i + 1;
}
Random.Shared.Shuffle(numeros);
int[] jogo = numeros[..6];

Array.Sort(jogo);
Console.WriteLine($"Jogo: {string.Join(" ", jogo)}");
Console.WriteLine($"6 números diferentes: {jogo.Distinct().Count() == 6}");
Console.WriteLine($"Todos entre 1 e 60: {Array.TrueForAll(jogo, n => n >= 1 && n <= 60)}");

O laço enche o array com 1, 2, 3… 60. O Shuffle embaralha, e a fatia [..6] (aula Índices e Intervalos) pega os 6 primeiros. Como cada número estava no array uma vez só, não tem como repetir. O Array.Sort só deixa o jogo mais fácil de ler, e as duas conferências usam o Distinct do LINQ (que tira os repetidos) e o TrueForAll da aula A Classe Array.


Resumo

  • Os números do Random são pseudoaleatórios: saem de uma conta, a partir de uma semente. Para jogos e sorteios, bastam.
  • Use Random.Shared: um sorteador pronto, com uma semente diferente a cada execução.
  • Next(min, max) sorteia de min até max − 1: um dado é Next(1, 7). Next(v.Length) sorteia um índice do array.
  • NextDouble() dá de 0 a 1; GetItems sorteia vários (com repetição); Shuffle embaralha (e, pegando os primeiros, sorteia sem repetição).
  • new Random(42) repete sempre a mesma sequência: bom para testes. No editor e no dotnet do Linux, a sequência deu igual. Crie o sorteador uma vez, fora do laço.
  • Para senhas e códigos de segurança, RandomNumberGenerator, nunca Random.