BigInteger è immutabile o no?
-
06-07-2019 - |
Domanda
In .NET 4 beta 2, c'è il nuovo spazio dei nomi Numerics con struct BigInteger
. La documentazione afferma che è un tipo immutabile, come mi sarei aspettato.
Ma sono un po 'confuso dall'operatore post-incremento ( ++
). Questo sembra indubbiamente mutare il valore. Il seguente ciclo while funziona:
static BigInteger Factorial(BigInteger n)
{
BigInteger result = BigInteger.One;
BigInteger b = BigInteger.One;
while (b <= n)
{
result = result * b;
b++; // immutable ?
}
return result;
}
Questo è ciò che MSDN ha da dire sull'operatore Increment:
Perché gli oggetti BigInteger lo sono immutabile, l'operatore Increment crea un nuovo oggetto BigInteger di cui il valore è uno in più rispetto al BigInteger oggetto rappresentato da valore. Pertanto, ripetute chiamate all'incremento potrebbe essere costoso.
Va ??bene, avrei capito se avessi dovuto usare b = b ++
ma apparentemente ++
da solo è sufficiente per cambiare un valore.
Qualche idea?
Soluzione
Gli operatori ++
e -
sono implementati in termini dei normali operatori +
e -
, così in realtà:
b++;
è equivalente a:
var temp = b;
b = b + 1;
<use temp for the expression where b++ was located>
Ora, come commentato, potrebbe sembrare che rompa l'immutabilità, ma non lo è.
Dovresti invece guardare questo codice nel modo seguente:
var temp = b;
b = BigInteger.op_Add(b, 1); // constructs a new BigInteger value
<use temp ...>
Questo lascerà in memoria due oggetti, il valore originale di BigInteger e il nuovo, ora indicato da b. Puoi facilmente verificare che questo sia ciò che accade con il seguente codice:
var x = b;
b++;
// now inspect the contents of x and b, and you'll notice that they differ
Quindi l'oggetto originale non è cambiato, quindi non rompe l'immutabilità, e per rispondere alla nuova parte della domanda, questo dovrebbe essere thread-safe.
Questa è la stessa cosa che succede alle stringhe:
String s1 = s2;
s2 += "More";
// now inspect s1 and s2, they will differ
Altri suggerimenti
Poiché BigInteger è immutabile, b ++ sarà equivalente a:
BigInteger temp=b;
b=temp+1;
Dopo questa operazione, la temperatura viene riciclata dal GC e la memoria viene liberata.
BigInteger b = BigInteger.One;
b++; // immutable ?
Nel tuo esempio b è una variabile, che è solo uno slot di memoria nel frame dello stack del metodo corrente. Viene inizializzato su One e b ++ accetta b, crea un nuovo BigInteger (con il valore incrementato) e lo restituisce. la variabile b ora ha lo stato dal nuovo BigInteger restituito.
Ad essere sincero, l'immutabilità come concetto è molto più chiara quando si tratta di tipi di riferimento, perché c'è un oggetto nell'heap il cui stato interno non cambia mai, quindi quando un'operazione / metodo restituisce un nuovo oggetto con stato diverso è in qualche modo ovvio (ad es. è possibile eseguire un controllo dell'uguaglianza di riferimento dell'oggetto con object.ReferenceEquals (oggetto, oggetto).
Per i tipi di valore non c'è alcun oggetto nell'heap, c'è solo lo slot in memoria che contiene i bit che sono il valore.
Ok, ma per quanto riguarda l'operatore di negazione unaria che è definito su BigInteger:
public static BigInteger operator -(BigInteger value)
{
value._sign = -value._sign;
return value;
}
sembra rompere il modello di immutabilità e mutare direttamente l'oggetto BigInteger. Quindi
b = -b;
modifica effettivamente un BigInteger esistente in posizione senza restituire un nuovo oggetto.