Perché i tipi di numero C # sono immutabili?

Perché int e double s sono immutabili? Qual è lo scopo di restituire un nuovo object ogni volta che si desidera modificare il valore?

Il motivo per cui lo chiedo è perché sto creando una class: BoundedInt , che ha un valore e un limite superiore e inferiore. Quindi mi stavo chiedendo: dovrei rendere immutabile anche questo tipo? (O dovrebbe essere una struct ?)

In primo luogo:

Qual è lo scopo di restituire un nuovo object ogni volta che si desidera modificare il valore?

Penso che potresti essere in errore su come funzionano i tipi di valore. Questa non è un’operazione costosa come potresti immaginare; è semplicemente la sovrascrittura dei dati (al contrario, ad esempio, dell’allocazione dynamic della nuova memoria).

In secondo luogo: ecco un esempio molto semplice del perché i numeri sono immutabili:

 5.Increase(1); Console.WriteLine(5); // What should happen here? 

Certo, questo è un esempio forzato. Consideriamo un paio di idee più coinvolgenti.

Tipo di riferimento mutevole

Innanzitutto, c’è questo: cosa succede se Integer fosse un tipo di riferimento mutevole?

 class Integer { public int Value; } 

Quindi potremmo avere un codice come questo:

 class Something { public Integer Integer { get; set; } } 

E:

 Integer x = new Integer { Value = 10 }; Something t1 = new Something(); t1.Integer = x; Something t2 = new Something(); t2.Integer = t1.Integer; t1.Integer.Value += 1; Console.WriteLine(t2.Integer.Value); // Would output 11 

Questo sembra sfidare l’intuizione: che la riga t2.Integer = t1.Integer copi semplicemente un valore (in realtà, lo fa, ma quel “valore” è in realtà un riferimento) e quindi t2.Integer rimarrebbe indipendente da t1.Integer

Tipo di valore mutabile

Questo potrebbe essere affrontato in un altro modo, ovviamente mantenendo Integer come un tipo di valore ma mantenendo la sua mutabilità:

 struct Integer { public int Value; // just for kicks public static implicit operator Integer(int value) { return new Integer { Value = value }; } } 

Ma ora diciamo che lo facciamo:

 Integer x = 10; Something t = new Something(); t.Integer = x; t.Integer.Value += 1; // This actually won't compile; but if it did, // it would be modifying a copy of t.Integer, leaving // the actual value at t.Integer unchanged. Console.WriteLine(t.Integer.Value); // would still output 10 

Fondamentalmente, l’immutabilità dei valori è qualcosa che è altamente intuitivo . L’opposto è altamente intuitivo.

Immagino che sia soggettivo, comunque, in tutta onestà;)

Come object mutabile, devi bloccare una variabile int prima di cambiarla (in qualsiasi codice multi-thread che scrive sul tuo int da thread separati).

Perché? Diciamo che stavi incrementando un int , come questo:

 myInt++ 

Sotto il cofano, questo è un numero a 32 bit. Teoricamente, su un computer a 32 bit è ansible aggiungerne 1 e questa operazione potrebbe essere atomica; cioè, sarebbe realizzato in un solo passaggio, perché sarebbe stato realizzato in un registro della CPU. Sfortunatamente non lo è; c’è più di questo.

Cosa succede se un altro thread ha mutato questo numero mentre era nel mezzo di essere incrementato? Il tuo numero verrebbe corrotto.

Tuttavia, se si crea una copia del proprio object protetta da thread prima di incrementarla, operare sulla copia thread-safe e restituire un nuovo object quando l’incremento è completo, si garantisce che il proprio incremento sia sicuro per i thread; non può essere influenzato da alcuna operazione sull’object originale che si svolge su altri thread, perché non si lavora più con l’object originale. In effetti, hai reso il tuo object immutabile.

Questo è il principio alla base della programmazione funzionale; rendendo gli oggetti immutabili e restituendo nuovi oggetti dalle funzioni, ottieni gratuitamente la sicurezza dei thread.

Le variabili intere sono mutabili. Tuttavia, i letterali interi sono costanti, quindi immutabili.

 int i = 0; // Mutation coming! i += 3; // The following line will not compile. 3 += 7; 

È ansible rendere un campo intero immutabile, usando readonly . Allo stesso modo, una proprietà intera potrebbe essere get-only.

Ha senso avere BoundedInt come un tipo mutabile perché rappresenta una variabile che in qualsiasi momento ha un valore specifico e quel valore può essere cambiato ma solo entro un certo intervallo.

Tuttavia gli stessi numeri interi non sono variabili, quindi non dovrebbero essere mutabili.

Qualsiasi cosa con semantica del valore dovrebbe essere immutabile in C #.

Le classi mutabili non possono avere semantica del valore perché non è ansible sovrascrivere l’operatore di assegnazione.

 MyClass o1=new MyClass(); MyClass o2=o1; o1.Mutate(); //o2 got mutated too //=> no value but reference semantics 

Le strutture mutevoli sono brutte perché puoi facilmente chiamare un metodo di muting su una variabile temporanea. In particolare le proprietà restituiscono variabili temporanee.

 MyStruct S1; MyStruct S2{get;set;} S1.Mutate(); //Changes S1 S2.Mutate();//Doesn't change S2 

Ecco perché non mi piace che la maggior parte delle librerie Vector utilizzino metodi di muting come Normalize nella loro struttura Vector.

Sto lavorando a un progetto accademico con Neural Networks. Queste reti eseguono calcoli pesanti con il doppio. Lo eseguo su Amazon Cloud per giorni su 32 server core. Quando si profila l’applicazione, il problema delle prestazioni migliori è l’assegnazione del doppio !! Sarebbe giusto avere uno spazio dei nomi dedicato con tipi mutabili. Le parole chiave “non sicure” possono essere applicate per ulteriore precauzione.