Gerando Números Aleatórios em C#
[[academy-video-youtube({"vid": "GFLPncbEi-w", "start_time": "0", "title": "Gerando Números Aleatórios em C#", "creator": "Tim Corey", "length": "10m 05s"})]]
Gerar números aleatórios em C# parece que deveria ser uma única linha, e em muitos casos é. Mas a linguagem oferece mais de uma maneira de produzir valores aleatórios, e as diferenças entre elas importam uma vez que você considera a segurança de thread, reprodutibilidade e uso. Escolher a abordagem errada pode introduzir bugs sutis no código multi-thread ou tornar um defeito relatado impossível de reproduzir.
No vídeo "Gerando Números Aleatórios em C#", Tim Corey percorre a clássica classe Random, explica por que existem valores de semente e apresenta Random.Shared como o padrão moderno. Cobriremos cada abordagem junto com o raciocínio por trás dela, para que você possa escolher a certa sem adivinhação. Se você já se perguntou por que há múltiplos caminhos para algo que parece tão simples, este artigo explica.
Configurando a Demonstração
[0:11 - 0:59] Tim trabalha dentro de um aplicativo console rodando no Visual Studio 2026 (atualmente em preview) com .NET 10. Ele observa que tudo demonstrado aqui se aplica igualmente a .NET 9 e Visual Studio 2022, então você pode acompanhar com qualquer ferramenta que já tenha instalada.
O layout da demonstração é um loop for que imprime dois valores aleatórios lado a lado em cada iteração. Ter duas saídas rodando em paralelo facilita observar se ambos os geradores se comportam independentemente ou produzem resultados correspondentes, uma distinção que se torna importante assim que valores de seed entram na jogada.
A Classe Random Clássica
[0:59 - 3:28] A abordagem original para gerar inteiros aleatórios em C# envolve criar uma instância da classe Random:
Random rng1 = new Random();
Random rng2 = new Random();
Random rng1 = new Random();
Random rng2 = new Random();
Cada instância mantém seu próprio estado interno. Chamar .Next(1, 101) em qualquer um produz um inteiro entre 1 e 100. Tim destaca um detalhe que confunde iniciantes: o valor mínimo é inclusivo, mas o máximo é exclusivo. Se você quer valores de 1 até 100, você passa 1 e 101, não 1 e 100.
int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);
int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);
Executar a aplicação confirma que ambas as instâncias produzem sequências diferentes. Esse resultado parece intuitivo, mas o que acontece quando ambas as instâncias compartilham o mesmo ponto de partida conta uma história diferente.
Um aviso importante sobre esta abordagem: instâncias individuais Random não são seguras para threads. Se sua aplicação realiza processamento paralelo e múltiplas threads acessam a mesma instância, o estado interno pode se corromper, produzindo zeros ou valores repetidos. A prática segura é criar uma instância por thread. Essa restrição é uma das razões pelas quais a linguagem mais tarde introduziu uma alternativa melhor.
Valores de Seed e Sequências Reproduzíveis
[3:28 - 6:00] Tim então passa uma seed explícita para ambos os construtores:
Random rng1 = new Random(25);
Random rng2 = new Random(25);
Random rng1 = new Random(25);
Random rng2 = new Random(25);
A saída muda drasticamente. Ambos os geradores agora produzem sequências idênticas: 79, 16, 25, 90, 50, 41, e assim por diante. Os números ainda são individualmente imprevisíveis se você não souber a seed, mas dado o mesmo valor inicial, a progressão é determinística.
Por que alguém iria querer isso? Tim dá um exemplo prático. Imagine um jogo que gera eventos aleatórios ao longo de uma sessão. Um jogador relata um bug, mas reproduzi-lo parece impossível porque os resultados foram randomizados. Se o jogo registrar a semente usada para aquela sessão, um desenvolvedor pode recriar a cadeia exata de decisões inicializando uma nova instância Random com o mesmo valor. A mesma lógica se aplica a cenários de testes unitários onde você precisa de saídas consistentes para escrever asserções confiáveis contra comportamentos randomizados.
Instâncias com seed fornecem aleatoriedade controlada: uma sequência que parece imprevisível mas pode ser reproduzida sob demanda. Essa capacidade é a razão pela qual o construtor clássico Random que aceita uma semente não foi depreciado, mesmo que uma API mais simples agora exista.
Random.Shared: O Padrão Moderno
[7:36 - 9:01] A partir do .NET 6, a abordagem recomendada para a maioria das gerações de números aleatórios é Random.Shared:
int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);
int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);
Não há instanciamento envolvido. Random.Shared é uma instância estática, segura para threads, gerenciada pelo tempo de execução. Você chama .Next() (ou qualquer um dos outros métodos na classe Random) e recebe um valor sem se preocupar com o tempo de vida do objeto ou concorrência.
Tim executa a demo duas vezes para provar o ponto. A primeira execução começa com 94 e 91; o segundo começa com 42 e 70. Diferentemente de uma instância com semente, Random.Shared parte de um estado inicial diferente cada vez que o processo é iniciado. Você não pode definir uma seed, o que significa que você não pode produzir uma sequência reproduzível através desta API. Essa é a troca: simplicidade e segurança em troca de abrir mão da reprodução determinística.
Além de .Next(), Random.Shared expõe métodos para gerar doubles, preencher arrays de bytes e embaralhar coleções. Para a vasta maioria do código de aplicação onde você precisa de um valor aleatório rápido sem cerimônia, esta única propriedade estática substitui o boilerplate de gerenciar suas próprias instâncias.
Escolhendo a Abordagem Certa
[9:01 - 9:30] Tim fecha com uma estrutura de decisão concisa. Para aleatoriedade do dia a dia (escolher um valor, embaralhar uma lista, selecionar um elemento aleatório), Random.Shared é a escolha certa. Não requer configuração, lida com concorrência e se comporta corretamente entre threads.
Quando você precisa de uma série repetível de saídas, seja para depuração, teste ou simulação de repetição, crie uma instância dedicada Random com uma semente conhecida. Lembre-se de que essas instâncias não são seguras para compartilhar entre threads.
E para qualquer coisa que envolva segurança (tokens, chaves, sais de senhas), nenhuma abordagem é apropriada. Tim direciona os espectadores para as bibliotecas criptográficas em System.Security.Cryptography, que produzem valores não apenas aleatórios, mas também resistentes à previsão.
Encerrando: API Simples, Diferenças Significativas
[9:30 - 9:50] O que torna este tópico enganador é quão pouco código ele demanda. Uma única linha pode gerar um número aleatório através de qualquer uma dessas abordagens. A complexidade não está na sintaxe, mas em entender quais garantias cada método oferece: segurança de thread, reprodutibilidade ou força criptográfica.
Conclusão
[9:50 - 10:05] Para recapitular: Random.Shared cobre a maioria das necessidades com configuração zero e segurança de thread embutida. Instâncias Random com semente permitem que você reproduza uma sequência específica quando a depuração ou o teste exigirem. Geradores criptográficos pertencem ao código sensível à segurança, onde a previsibilidade é uma vulnerabilidade, não uma característica.
Da próxima vez que procurar um número aleatório em C#, a decisão se resume a uma pergunta: você precisa reproduzir esta sequência mais tarde? Se a resposta for não, Random.Shared é tudo o que você precisa.
Dica de exemplo: Ao chamar Random.Shared.Next(min, max), observe que max é exclusivo. Um intervalo de 1 a 100 requer passar 1 e 101. Esta margem de um é aplicável também às instâncias semeadas.
Assista ao vídeo completo no YouTube de Tim Corey e obtenha mais insights sobre fundamentos do C#.
