2012-03-18 10 views
16

Sto migrando un metodo che viene utilizzato per la decodifica da .NET Framework 1.1 a .NET Framework 4. Ho notato che l'implementazione di Random è stata modificata. Quindi, dato lo stesso seme, Random.NextBytes restituisce risultati diversi.Cambio implementazione su .NET's Random()

Quindi se eseguo il seguente codice.

byte[] bytes = new byte[4]; 
System.Random random = new System.Random(50); 
random.NextBytes(bytes); 

for(int i=0; i< bytes.Length; i++) 
{ 
    Console.WriteLine("bytes[" + i + "] = " + bytes[i]); 
} 

Sotto .NET Framework 1.1 restituisce:

bytes[0] = 216 
bytes[1] = 124 
bytes[2] = 183 
bytes[3] = 58 

Sotto .NET framework 4 restituisce:

bytes[0] = 154 
bytes[1] = 49 
bytes[2] = 183 
bytes[3] = 48 

Qual è il modo migliore per risolvere questo problema?

+10

Qual è esattamente il problema? Il tuo programma dipende dalla particolare generazione di numeri casuali? – Stilgar

+1

Sono con Stilgar. Random ha lo scopo di produrre risultati pseudo-casuali. Se hai bisogno di byte specifici, perché non inserire questi byte nel tuo codice? @ Moo-juice, ma se stai eseguendo l'aggiornamento a un framework successivo, tutti useranno la stessa implementazione dopo l'aggiornamento ...? – sheikhjabootie

+0

@ Moo-Juice sicuro. Assicurati che le versioni siano compatibili prima di consentire ai client di connettersi. I giochi hanno fatto questo tipo di versioning per decenni. – Stilgar

risposta

17

È possibile utilizzare Reflector per copiare la classe Random da 1.1 mscorlib.

public class Random1_1 
{ 
    // Fields 
    private int inext; 
    private int inextp; 
    private const int MBIG = 0x7fffffff; 
    private const int MSEED = 0x9a4ec86; 
    private const int MZ = 0x0; 
    private int[] SeedArray; 

    // Methods 
    public Random1_1() 
     : this(Environment.TickCount) 
    { 
    } 

    public Random1_1(int Seed) 
    { 
     this.SeedArray = new int[0x38]; 
     int num2 = 0x9a4ec86 - Math.Abs(Seed); 
     this.SeedArray[0x37] = num2; 
     int num3 = 0x1; 
     for (int i = 0x1; i < 0x37; i++) 
     { 
      int index = (0x15 * i) % 0x37; 
      this.SeedArray[index] = num3; 
      num3 = num2 - num3; 
      if (num3 < 0x0) 
      { 
       num3 += 0x7fffffff; 
      } 
      num2 = this.SeedArray[index]; 
     } 
     for (int j = 0x1; j < 0x5; j++) 
     { 
      for (int k = 0x1; k < 0x38; k++) 
      { 
       this.SeedArray[k] -= this.SeedArray[0x1 + ((k + 0x1e) % 0x37)]; 
       if (this.SeedArray[k] < 0x0) 
       { 
        this.SeedArray[k] += 0x7fffffff; 
       } 
      } 
     } 
     this.inext = 0x0; 
     this.inextp = 0x15; 
     Seed = 0x1; 
    } 

    public virtual int Next() 
    { 
     return (int)(this.Sample() * 2147483647.0); 
    } 

    public virtual int Next(int maxValue) 
    { 
     if (maxValue < 0x0) 
     { 
      throw new ArgumentOutOfRangeException("maxValue"); 
     } 
     return (int)(this.Sample() * maxValue); 
    } 

    public virtual int Next(int minValue, int maxValue) 
    { 
     if (minValue > maxValue) 
     { 
      throw new ArgumentOutOfRangeException("minValue"); 
     } 
     int num = maxValue - minValue; 
     if (num < 0x0) 
     { 
      long num2 = maxValue - minValue; 
      return (((int)((long)(this.Sample() * num2))) + minValue); 
     } 
     return (((int)(this.Sample() * num)) + minValue); 
    } 

    public virtual void NextBytes(byte[] buffer) 
    { 
     if (buffer == null) 
     { 
      throw new ArgumentNullException("buffer"); 
     } 
     for (int i = 0x0; i < buffer.Length; i++) 
     { 
      buffer[i] = (byte)(this.Sample() * 256.0); 
     } 
    } 

    public virtual double NextDouble() 
    { 
     return this.Sample(); 
    } 

    protected virtual double Sample() 
    { 
     int inext = this.inext; 
     int inextp = this.inextp; 
     if (++inext >= 0x38) 
     { 
      inext = 0x1; 
     } 
     if (++inextp >= 0x38) 
     { 
      inextp = 0x1; 
     } 
     int num = this.SeedArray[inext] - this.SeedArray[inextp]; 
     if (num < 0x0) 
     { 
      num += 0x7fffffff; 
     } 
     this.SeedArray[inext] = num; 
     this.inext = inext; 
     this.inextp = inextp; 
     return (num * 4.6566128752457969E-10); 
    } 
} 

Testato e fornisce l'output desiderato.

+0

Per inciso, qual è la differenza tra i due, se li rifletti? –

+0

La cosa principale sembra essere che hanno aggiunto il supporto per campioni più grandi. Ecco la classe casuale da .net 4.0. http://pastebin.com/stFuTzCk. – Will

+11

Legalmente dubbioso. Non puoi decompilare e utilizzare il codice di altre persone. – CodesInChaos

27

Questo non è un problema con Random, soddisfa perfettamente l'interfaccia documentata. Questo è un problema con il tuo software che si basa su un dettaglio di implementazione. Impara da questo errore e non farlo di nuovo.

Per quanto riguarda la risoluzione del problema, è possibile implementare la propria versione di 1.1 di generazione di numeri pseudo per la decodifica e quindi implementare un nuovo algoritmo di codifica/decodifica che non si basa sul comportamento instabile (come ad esempio la realizzazione di Random o GetHashCode) per la nuova versione del software.

+2

Questo è molto ingiusto; Memorizzare il seme usato è stato un modo affidabile di usare Random dal momento che posso ricordare. –

+11

Non è affatto ingiusto. L'algoritmo non è documentato e quindi è un dettaglio di implementazione. Questo è un concetto fondamentale che riguarda l'utilizzo praticamente di qualsiasi API appropriata: non fare affidamento sui dettagli di implementazione o potresti bruciarti. Ciò che è ingiusto è usare un'API fornita e quindi forzare quell'API a non cambiare mai la sua implementazione in qualcosa di superiore, perché ti affidavi a stupidi dettagli di implementazione invece del contratto pubblicato. –

+4

@RussC davvero? Dalla documentazione: "L'implementazione del generatore di numeri casuali nella classe Random non è garantita per essere la stessa nelle principali versioni di .NET Framework.Di conseguenza, il codice dell'applicazione non dovrebbe presumere che lo stesso seme genererà la stessa sequenza pseudo-casuale in diverse versioni di .NET Framework. " – Stilgar

3

Se sei assolutamente dipendente dalla versione .NET 1.1 di Random, l'unica cosa che mi viene in mente è creare un nuovo assembly con target 1.1 e chiamarlo dall'applicazione .NET 4 aggiornata.

Tuttavia, puoi spiegare perché è così essenziale per te mantenere questo seme? Potrebbe esserci un modo migliore.

+1

Penso che la risposta di High sia probabilmente il modo di andare qui. –

+0

L'assembly 1.1 verrebbe ancora eseguito nella CLI superiore, no? –

+0

Se il framework 1.1 è stato installato, no. –

0

In alternativa, potrei suggerire di utilizzare la classe System.Security.Cryptography.RandomNumberGenerator per generare array di byte casuali crittograficamente forti?

RandomNumberGenerator rng = RandomNumberGenerator.Create(); 
byte[] bytes = new byte[128]; 
rng.GetBytes(bytes); 

Mi unirò al resto dei commenti e dirò che fare affidamento su un'implementazione non documentata è negativo. Ancora di più, se si sta effettivamente facendo affidamento su una prevedibile "casualità" - se si sta usando questo per qualcosa che dovrebbe essere "sicuro" - è totalmente sbagliato.

+0

"fare affidamento su un'implementazione non documentata è sbagliato". Sei pazzo? Ogni singolo metodo e proprietà in tutte le classi .NET standard hanno implementazioni non documentate. Ciò che è importante è il risultato, che dovrebbe * non * cambiare tra le versioni. – adelphus

2

Nessuna risposta qui, ma contrariamente a molte persone qui non penso che documentare un comportamento ridicolo sia sufficiente a giustificarlo.

Perché perché fornire un meccanismo di semina in primo luogo? Bene, ti dirò: in modo che tu possa sempre riprodurre una sequenza casuale da un singolo seme piuttosto che dover persistere forse milioni di numeri casuali. Nota che ho detto "sempre" e non "finché non si aggiorna alla prossima versione di .NET". Non essendo coerenti tra le versioni, i generatori numerici casuali di .NET non forniscono questa funzionalità. Microsoft avrebbe dovuto fare un lavoro migliore per implementarlo (o non averlo implementato affatto) invece di documentare semplicemente il comportamento difettoso.

E a proposito, sebbene l'algoritmo sia effettivamente un dettaglio di implementazione, come mai è possibile chiamare il risultato di un metodo per richiamare un dettaglio di implementazione? Dovrei davvero controllare la documentazione di ogni metodo nel framework .NET per essere sicuro che nella prossima versione non rischi di ottenere un risultato diverso dalla concatenazione di due stringhe o dal calcolo di una radice quadrata?

Quindi, a mio parere, quello che abbiamo qui è semplicemente un generatore di numeri casuali mal implementato. E naturalmente l'intero problema avrebbe potuto essere facilmente evitato dando un nuovo nome alla nuova funzionalità (basata sulla nuova implementazione).

+1

"perché dovresti fornire un meccanismo di semina in primo luogo?" Perché è così che funzionano i generatori di numeri pseudocasuali! Non sono in realtà casuali, ma generano una sequenza di numeri basata su ... un seme. Nessun seme, nessun numero pseudocasuale. Ecco perchè. –