Minha função C# estática está jogando comigo ... total estranho!
-
26-09-2019 - |
Pergunta
Então, eu escrevi um pequeno método fácil e, pelo que inicialmente pensei, em C#. Este método estático deve ser usado como um gerador de sugestão de senha simples, e o código se parece com o seguinte:
public static string CreateRandomPassword(int outputLength, string source = "")
{
var output = string.Empty;
for (var i = 0; i < outputLength; i++)
{
var randomObj = new Random();
output += source.Substring(randomObj.Next(source.Length), 1);
}
return output;
}
Eu chamo essa função assim:
var randomPassword = StringHelper.CreateRandomPassword(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
Agora, esse método quase sempre retorna seqüências aleatórias como "aaaaaaa", "bbbbbb", "888888" etc., onde eu pensei que deveria devolver strings como "A8JK2a", "82mok7" etc.
No entanto, e aqui está a parte estranha; Se eu colocar um ponto de interrupção e passar por essa linha de iteração por linha, recebo o tipo correto de senha em troca. Em 100% dos outros casos, quando não estou depurando, isso me dá uma porcaria como "aaaaaa", "666666", etc ..
Como isso é possível? Qualquer sugestão é muito apreciada! :-)
BTW, meu sistema: Visual Studio 2010, C# 4.0, ASP.NET MVC 3 RTM Project w/ Asp.Net Development Server. Não testei este código em nenhum outro ambiente.
Solução
Mova a declaração para o Randomobj fora do loop. Quando você está depurando, ele cria uma nova semente a cada vez, porque há diferença de tempo suficiente para que a semente seja diferente. Mas quando você não está depurando, o tempo de semente é basicamente o mesmo para cada iteração do loop, por isso está fornecendo o mesmo valor de início a cada vez.
E um NIT menor-é um bom hábito usar um StringBuilder em vez de uma string para esse tipo de coisa, para que você não precise reinicializar o espaço de memória toda vez que anexar um personagem à string.
Em outras palavras, assim:
public static string CreateRandomPassword(int outputLength, string source = "")
{
var output = new StringBuilder();
var randomObj = new Random();
for (var i = 0; i < outputLength; i++)
{
output.Append(source.Substring(randomObj.Next(source.Length), 1));
}
return output.ToString();
}
Outras dicas
O comportamento que você está vendo é porque Random
é baseado no tempo, e quando você não está depurando, ele voa por todas as 5 iterações no mesmo momento (mais ou menos). Então você está pedindo o primeiro número aleatório da mesma semente. Quando você está depurando, leva o tempo suficiente para obter uma nova semente a cada vez.
Mover a declaração de Random
Fora do loop:
var randomObj = new Random();
for (var i = 0; i < outputLength; i++)
{
output += source.Substring(randomObj.Next(source.Length), 1);
}
Agora você está Avançando a 5 passos de uma semente aleatória ao invés de movendo -se a um passo da mesma semente aleatória 5 vezes.
Você está instanciando uma nova instância de aleatoriamente () em cada iteração através do loop com uma nova semente dependente do tempo. Dada a granularidade do relógio do sistema e a velocidade das CPUs modernas, isso garante que você reinicie a sequência pseudo-aleatória repetidamente com a mesma semente.
Experimente algo como o seguinte, embora se você for um thread único, pode omitir com segurança o bloqueio ():
private static Random randomBits = new Random() ;
public static string CreateRandomPassword(int outputLength, string source = "")
{
StringBuilder sb = new StringBuilder(outputLength) ;
lock ( randomBits )
{
while ( sb.Length < outputLength )
{
sb.Append( randomBits.Next( source.Length) , 1 ) ;
}
}
return sb.ToString() ;
}
Você apenas instancia o RNG uma vez. Todo mundo desenha bits do mesmo RNG, por isso deve se comportar muito mais como uma fonte de entropia. Se você precisar de repetibilidade para teste, use a sobrecarga de construtor aleatório que permite fornecer a semente. Mesma semente == mesma sequência pseudo-aleatória.