Domanda

Mi capita spesso letto che struct s dovrebbe essere immutabile - non è vero per definizione

?

Ti consideri int essere immutabili?

int i = 0;
i = i + 123;

Sembra a posto - otteniamo una nuova i e assegniamo di nuovo a Point. Che dire di questo?

i++;

Va bene, possiamo pensare ad esso come una scorciatoia.

i = i + 1;

E il (1, 2) Point.Offset()?

Point p = new Point(1, 2);
p.Offset(3, 4);

Tutto questo è veramente mutare il punto ref? Non dovremmo pensare ad esso come una scorciatoia per il seguente con p = p.Offset(3, 4); restituzione di un nuovo punto?

p = p.Offset(3, 4);

Lo sfondo di questo pensiero è questo - come può un tipo di valore senza identità essere mutevole? Bisogna guardare almeno due volte per determinare se è cambiato. Ma come si può fare questo senza un'identità?

Non voglio complicare il ragionamento su questo considerando p.Offset(3, 4); parametri e la boxe. Sono anche consapevole del fatto che Foo esprime immutabilità molto meglio di <=> fa. Ma la domanda rimane -? Non sono valore tipi immutabili, per definizione,

Aggiorna

Credo che ci siano almeno due concetti coinvolti -. La mutevolezza di una variabile o campo e la mutevolezza del valore di una variabile

public class Foo
{
    private Point point;
    private readonly Point readOnlyPoint;

    public Foo()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2);
    }

    public void Bar()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2); // Does not compile.

        this.point.Offset(3, 4); // Is now (4, 6).
        this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
    }
}

Nell'esempio abbiamo a campi - uno mutevole e uno immutabile. Poiché un campo tipo valore contiene il valore intero, un tipo di valore memorizzato in un campo immutabile deve essere immutabile, anche. Sono ancora abbastanza sorpreso dal risultato -. Non ho Exspect il campo di sola lettura di rimanere non modificato

Variabili (oltre costanti) sono allways mutevoli, quindi implicano alcuna restrizione alla mutabilità di tipi di valore.


La risposta sembra non essere che dritto in avanti così sarò riformulare la domanda.

Dato il seguente.

public struct Foo
{
    public void DoStuff(whatEverArgumentsYouLike)
    {
        // Do what ever you like to do.
    }

    // Put in everything you like - fields, constants, methods, properties ...
}

Si può dare una versione completa di <=> ed un esempio di utilizzo - che può includere <=> parametri e la boxe - in modo che non è possibile riscrivere tutte le occorrenze di

foo.DoStuff(whatEverArgumentsYouLike);

con

foo = foo.DoStuff(whatEverArgumentsYouLike);
È stato utile?

Soluzione

  

Un oggetto è immutabile se il suo stato   non cambia una volta che l'oggetto ha   stato creato.

Risposta breve: No, i tipi di valore non sono immutabili per definizione. Entrambe le strutture e le classi possono essere sia mutevole o immutabile. Tutte e quattro le combinazioni sono possibili. Se una struttura o di classe ha campi non di sola lettura pubblica, le proprietà pubbliche con setter, o metodi che fissano campi privati, è mutevole perché si può cambiare il suo stato senza creare una nuova istanza di quel tipo.


Risposta lunga: Prima di tutto, la questione della immutabilità si applica solo a struct o classi con campi o proprietà. I tipi più elementari (numeri, stringhe e null) sono intrinsecamente immutabili perché non c'è nulla (campo / proprietà) per cambiare su di loro. A 5 è un 5 è un 5. Ogni operazione sul 5 restituisce solo un altro valore immutabile.

È possibile creare le strutture mutabili come System.Drawing.Point. Sia X e Y hanno setter che modificano i campi della struct:

Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5

Alcune persone sembrano confondere immutablity con il fatto che i tipi di valore sono passati per valore (da qui il loro nome) e non con riferimento.

void Main()
{
    Point p1 = new Point(0, 0);
    SetX(p1, 5);
    Console.WriteLine(p1.ToString());
}

void SetX(Point p2, int value)
{
    p2.X = value;
}

In questo caso Console.WriteLine() scrive "{X=0,Y=0}". Qui p1 non è stato modificato a causa SetX() modificato p2 che è un copia di (5, 0). Ciò accade perché Offset(3,4) è un tipo di valore , non perché è immutabile (non è).

Perché dovrebbe i tipi di valore essere immutabili? Un sacco di motivi ... Vedere questa domanda . Per lo più è perché i tipi di valore mutabili portano a tutti i tipi di insetti non-così-evidenti. Nell'esempio sopra il programmatore si sarebbe aspettato di essere Point readOnlyPoint dopo aver chiamato Offset(). Oppure immaginate l'ordinamento per un valore che può successivamente cambiare. Allora la vostra raccolta differenziata non sarà più ordinata come previsto. Lo stesso vale per i dizionari e hash. Il Fabulous Eric Lippert ( blog ) ha scritto un serie su immutabilità e perché crede che sia il futuro di C #. href="http://blogs.msdn.com/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx" Ecco uno dei suoi esempi che consente si "modifica" di una sola lettura variabile.


UPDATE: il tuo esempio con:

this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).

è esattamente la cosa Lippert cui al suo post su come modificare Variabili di lettura. foo in realtà modificato un otherFoo, ma era un copia di DoStuff(), e non è mai stato assegnato a nulla, quindi è perso.

e che è il motivo per i tipi di valore mutabili sono il male: Essi consentono di pensare si sta modificando qualcosa, quando a volte in realtà si sta modificando una copia, che porta ad errori inaspettati . Se IFoo era immutabile, foo1 sarebbe dovuto tornare una nuova foo2, e non sarebbe stato in grado di assegnare a foo3. E poi si va "Oh giusto, è di sola lettura per un motivo. Perché stavo cercando di cambiarlo? Meno male che il compilatore mi ha fermato adesso."


UPDATE: A proposito della richiesta riformulato ... Credo di sapere che cosa vuoi arrivare. In un certo senso, si può "pensare" di struct come internamente immutabile, che modifica una struttura è la stessa come la sua sostituzione con una copia modificata. Potrebbe anche essere quello che il CLR fa internamente nella memoria, per quanto ne so. (Ecco come funziona la memoria flash. Non puoi modificare i pochi byte, è necessario leggere un intero blocco di kilobyte in memoria, modificare i pochi che si desidera, e scrivere l'intero blocco posteriore.) Tuttavia, anche se erano "internamente immutabili ", che è un dettaglio di implementazione e per noi sviluppatori come utenti di struct (loro interfaccia API o, se si vuole), che possono essere cambiato. Non possiamo ignorare questo fatto e "pensare a loro come immutabili".

In un commento lei ha detto "non si può avere un riferimento al valore di campo o variabile". Si stanno assumendo che ogni variabile struct ha una copia diversa, in modo che modificando una copia non influenza gli altri. Questo non è del tutto vero. Le linee segnate qui di seguito non sono sostituibili se ...

interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }

IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2

Linee # 1 e # 2 non hanno gli stessi risultati ... Perché? Perché <=> e <=> fare riferimento al stessa istanza in scatola di Foo. Qualunque cosa è cambiato in <=> in linea # 1 riflette in <=>. Linea # 2 sostituisce <=> con un nuovo valore e non fa nulla per <=> (assumendo che <=> restituisce una nuova istanza <=> e non modifica <=> stesso).

Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance

Modifica <=> non influirà <=> o <=>. Modifica <=> rifletterà in <=>, ma non in <=>. Modifica <=> rifletterà in <=> ma non in <=>.

Confusione? Stick per i tipi di valore immutabili e si elimina il bisogno di modificare qualsiasi di loro.


UPDATE: fisso errore di battitura nel primo esempio di codice

Altri suggerimenti

tipi mutevolezza e valore sono due cose separate.

Definire un tipo come un tipo di valore, indica che il runtime copiare i valori anziché un riferimento al runtime. Mutabilità, d'altra parte, dipende l'attuazione, e ogni classe può implementare come vuole.

È possibile scrivere le strutture che sono mutabili, ma è delle migliori pratiche per rendere i tipi di valore immutabile.

Per esempio DateTime crea sempre nuove istanze quando fare qualsiasi operazione. Point è mutevole e può essere modificato.

Per rispondere alla tua domanda: No, non sono immutabili, per definizione, dipende dal caso se devono essere mutevoli o no. Per esempio, se devono servire chiavi di un dizionario, dovrebbero essere immutabili.

Se si prende la logica abbastanza lontano, quindi tutti tipi sono immutabili. Quando si modifica un tipo di riferimento, si potrebbe sostenere che si sta davvero scrivendo un nuovo oggetto per lo stesso indirizzo, piuttosto che modificare nulla.

In alternativa si potrebbe sostenere che tutto è mutevole, in qualsiasi lingua, perché a volte la memoria che era stato precedentemente usato per una cosa, verrà sovrascritto da un altro.

Con abbastanza astrazioni, e ignorando caratteristiche del linguaggio abbastanza, si può arrivare a una conclusione che ti piace.

E che non coglie il punto. Secondo spec .NET, i tipi di valore sono mutabili. È possibile modificarlo.

int i = 0;
Console.WriteLine(i); // will print 0, so here, i is 0
++i;
Console.WriteLine(i); // will print 1, so here, i is 1

, ma è sempre lo stesso i. Il i variabile è dichiarata solo una volta. Tutto ciò che accade ad essa dopo questa dichiarazione è una modifica.

In qualcosa di simile a un linguaggio funzionale con le variabili immutabili, questo non sarebbe legale. Il ++ non sarebbe possibile. Una volta che una variabile è stata dichiarata, ha un valore fisso.

In .NET, che non è il caso, non c'è nulla da impedirmi di modificare il set dopo che è stato dichiarato.

Dopo averci pensato un po 'di più, ecco un altro esempio che potrebbe essere migliore:

struct S {
  public S(int i) { this.i = i == 43 ? 0 : i; }
  private int i;
  public void set(int i) { 
    Console.WriteLine("Hello World");
    this.i = i;
  }
}

void Foo {
  var s = new S(42); // Create an instance of S, internally storing the value 42
  s.set(43); // What happens here?
}

L'ultima riga, secondo la tua logica, potremmo dire che abbiamo effettivamente costruire un nuovo oggetto, e sovrascrivere il vecchio con quel valore. Ma non è possibile! Per costruire un nuovo oggetto, il compilatore deve impostare la variabile di s.i a 42. Ma è privato! È accessibile solo attraverso un costruttore definito dall'utente, che non consente esplicitamente il valore 43 (impostando a 0 invece), e quindi attraverso il nostro set() metodo, che ha un brutto effetto collaterale. Il compilatore non ha modo di solo la creazione di un nuovo oggetto con i valori che gli piace. L'unico modo in cui <=> può essere impostata a 43 è di modifica l'oggetto corrente chiamando <=>. Il compilatore non può semplicemente farlo, perché sarebbe cambiare il comportamento del programma (sarebbe stampare alla console)

Quindi per tutti le strutture di essere immutabile, il compilatore dovrebbe barare e rompere le regole della lingua. E, naturalmente, se siamo disposti a rompere le regole, possiamo provare niente. Ho potuto dimostrare che tutti gli interi sono uguali troppo, o che la definizione di una nuova classe farò il computer per prendere fuoco. Finché restiamo nel rispetto delle regole del linguaggio, le strutture sono mutabili.

  

Non voglio complicare il ragionamento   su questo considerando ref   parametri e la boxe. Sono anche consapevole   che esprime p = p.Offset(3, 4);   immutabilità molto meglio di   p.Offset(3, 4); fa. Ma il   domanda rimane - non sono i tipi di valore   immutabile, per definizione?

Bene, allora non stai realmente operante nel mondo reale, vero? In pratica, la propensione dei tipi di valore per fare copie di se stessi che si muovono tra le funzioni si articoli con immutabilità, ma non sono in realtà immutabili se non li fai immutabile, dal momento che, come lei ha sottolineato, è possibile utilizzare i riferimenti a loro solo come qualsiasi altra cosa.

  

Non sono tipi di valore immutabili per definizione?

No, non sei. Se si guarda alla struct System.Drawing.Point per esempio, ha un setter e un getter sul suo X proprietà

Tuttavia può essere vero che tutti i tipi di valore dovrebbe essere definito con le API immutabili.

Credo che la confusione è che se si dispone di un tipo di riferimento che dovrebbe agire come un tipo di valore è una buona idea per renderlo immutabile. Una delle differenze principali tra i tipi di valore e tipi di riferimento è una modifica effettuata tramite un nome su un tipo Rif può apparire in altro nome. Questo non accade con i tipi di valori:

public class foo
{
    public int x;
}

public struct bar
{
    public int x;
}


public class MyClass
{
    public static void Main()
    {
        foo a = new foo();
        bar b = new bar();

        a.x = 1;
        b.x = 1;

        foo a2 = a;
        bar b2 = b;

        a.x = 2;
        b.x = 2;

        Console.WriteLine( "a2.x == {0}", a2.x);
        Console.WriteLine( "b2.x == {0}", b2.x);
    }
}

produce:

a2.x == 2
b2.x == 1

Ora, se si dispone di un tipo che si desidera avere la semantica di valore, ma non si vuole effettivamente fare un tipo di valore - forse perché lo stoccaggio richiede è troppo o qualsiasi altra cosa, si dovrebbe considerare che l'immutabilità è parte del disegno. Con un tipo di ref immutabile, qualsiasi modifica apportata a un riferimento esistente produce un nuovo oggetto invece di cambiamento quello esistente, in modo da ottenere il comportamento del tipo di valore che qualsiasi valore si tiene in mano non può essere modificato attraverso qualche altro nome.

Naturalmente la classe System.String è un ottimo esempio di tale comportamento.

L'anno scorso ho scritto un post sul blog per quanto riguarda i problemi che si possono incontrare non facendo struct  immutabili.

il post completo può essere letto qui

Questo è un esempio di come le cose possono andare terribilmente male:

//Struct declaration:

struct MyStruct
{
  public int Value = 0;

  public void Update(int i) { Value = i; }
}

Esempio di codice:

MyStruct[] list = new MyStruct[5];

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

L'output di questo codice è:

0 0 0 0 0
1 2 3 4 5

Ora facciamo lo stesso, ma sostituire l'array per un generico List<>:

List<MyStruct> list = new List<MyStruct>(new MyStruct[5]); 

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

L'output è:

0 0 0 0 0
0 0 0 0 0

La spiegazione è molto semplice. No, non è boxe / unboxing ...

Quando si accede elementi da un array, il runtime otterrà gli elementi dell'array direttamente, in modo che il metodo di aggiornamento () lavora sulla voce matrice stessa. Ciò significa che le si struct nella matrice vengono aggiornati.

Nel secondo esempio, abbiamo usato un <=> generico. Cosa succede quando si accede un elemento specifico? Ebbene, la proprietà indicizzatore si chiama, che è un metodo. I tipi di valore sono sempre copiati quando ritornato da un metodo, quindi questo è esattamente ciò che accade: il metodo indicizzatore della lista recupera lo struct da un array interno e lo restituisce al chiamante. Poiché si tratta di un tipo di valore, sarà effettuata una copia, e il metodo Update () sarà chiamato sulla copia, che ovviamente non ha alcun effetto sugli elementi originali della lista.

In altre parole, assicurarsi sempre che i tuoi struct sono immutabili, perché non sei mai sicuro di quando verrà fatta una copia. La maggior parte del tempo è ovvio, ma in alcuni casi si può davvero stupire ...

No, non lo sono. Esempio:

Point p = new Point (3,4);
Point p2 = p;
p.moveTo (5,7);

In questo esempio moveTo() è un sul posto funzionamento. Cambia la struttura che nasconde dietro il riferimento p. Si può vedere che da un'occhiata a p2: La sua posizione sarà anche cambiato. Con le strutture immutabili, Point avrebbe dovuto restituire una nuova struttura:

p = p.moveTo (5,7);

Ora, i è immutabile e quando si crea un riferimento ad esso qualsiasi punto del codice, non avrà nessuna sorpresa. Diamo un'occhiata a 5:

int i = 5;
int j = i;
i = 1;

Questo è diverso. int non è immutabile, è <=>. E il secondo incarico non copia un riferimento alla struttura che contiene <=> ma copia il contenuto di <=>. Così dietro le quinte, qualcosa di completamente diverso accade: È possibile ottenere una copia completa della variabile anziché soltanto una copia l'indirizzo in memoria (il riferimento)

.

Un equivalente con oggetti sarebbe il costruttore di copia:

Point p = new Point (3,4);
Point p2 = new Point (p);

Qui, la struttura interna di <=> viene copiato in un nuovo oggetto / struttura e <=> conterrà il riferimento a esso. Ma questa è un'operazione piuttosto costoso (a differenza l'assegnazione intero sopra), che è il motivo per cui la maggior parte dei linguaggi di programmazione fanno distinzione.

Come i computer diventano più potenti e ottenere più memoria, questa distinzione sta per andare via perché provoca una quantità enorme di bug e problemi. Nella generazione successiva, ci sarà solo oggetti immutabili, qualsiasi operazione sarà protetto da una transazione e anche un <=> sarà un oggetto pieno soffiato. Proprio come la raccolta dei rifiuti, sarà un grande passo avanti in termini di stabilità del programma, causare un sacco di dolore nei primi anni, ma permetterà di scrivere software affidabile. Oggi, i computer semplicemente non sono abbastanza veloce per questo.

No, i tipi di valore sono non immutabile per definizione.

In primo luogo, vorrei meglio ho fatto la domanda "fare i tipi di valore si comportano come i tipi immutabili?" invece di chiedere se sono immutabili - Presumo che ciò ha causato un sacco di confusione

.
struct MutableStruct
{
    private int state;

    public MutableStruct(int state) { this.state = state; }

    public void ChangeState() { this.state++; }
}

struct ImmutableStruct
{
    private readonly int state;

    public MutableStruct(int state) { this.state = state; }

    public ImmutableStruct ChangeState()
    {
        return new ImmutableStruct(this.state + 1);
    }
}

[Continua ...]

Per definire se un tipo è mutevole o immutabile, occorre definire che cosa "tipo" si riferisce. Quando viene dichiarata una posizione di tipo riferimento stoccaggio, la dichiarazione solo alloca spazio per contenere un riferimento a un oggetto memorizzato altrove; la dichiarazione non crea l'oggetto reale in questione. Tuttavia, nella maggior parte dei contesti in cui si parla di particolari tipi di riferimento, uno non verranno parlando di un posizione di memoria che contiene un riferimento , ma l'oggetto identificato da quella di riferimento . Il fatto che si può scrivere in una posizione di memorizzazione possesso di un riferimento a un oggetto implica in alcun modo che l'oggetto stesso è mutevole.

Al contrario, quando viene dichiarato un luogo di tipo di valore di memorizzazione, il sistema assegnerà all'interno che le posizioni di memorizzazione posizione di memoria annidati per ogni campo pubblico o privato tenuto da che tipo di valore. Tutto ciò che riguarda il tipo di valore è tenuto in quella posizione di archiviazione. Se si definisce una variabile di tipo foo Point e dei suoi due campi, X e Y, contengono rispettivamente 3 e 6. Se si definisce "istanza" di MyPoint = new Point(5,8) in X=5 come la coppia di campi , tale istanza sarà mutabile se e solo se Y=8 è mutevole. Se si definisce un'istanza di MyPoint come valori tenuta in quei settori ( "3,6"), allora tale esempio è per definizione immutabile, poiché cambiare uno dei campi causerebbe myPoints[] di tenere una diversa istanza.

Credo che sia più utile pensare a un tipo di valore "istanza" come i campi, piuttosto che i valori in loro possesso. Con tale definizione, qualsiasi tipo di valore memorizzato in una posizione di archiviazione mutevole, e per i quali esiste un valore non predefinito, sarà sempre essere mutevole, a prescindere da come si dichiara. Una dichiarazione myPoints[0].X costruisce una nuova istanza di <=>, con campi <=> e <=>, e poi può mutare <=> sostituendo i valori nei suoi campi con quelli del recente creazione <=>. Anche se uno struct fornisce alcun modo per modificare uno qualsiasi dei suoi settori di fuori del suo costruttore, non c'è modo un tipo struct può proteggere un'istanza di avere tutti i relativi campi sovrascritti con il contenuto di un altro esempio.

Per inciso, un semplice esempio di uno struct mutevole può raggiungere semantica non ottenibili con altri mezzi: Supponendo <=> è una matrice a elemento singolo che è accessibile da più thread, hanno venti thread contemporaneamente eseguire il codice:

Threading.Interlocked.Increment(myPoints[0].X);

Se <=> inizia uguale a zero e venti fili eseguire il codice precedente, anche simultaneamente o no, <=> sarà uguale venti. Se si dovesse tentare di imitare il codice precedente con:

myPoints[0] = new Point(myPoints[0].X + 1, myPoints[0].Y);

quindi se qualsiasi thread leggere <=> tra il momento in un altro thread lesse e scrive di nuovo il valore rivisto, i risultati del l'incremento sarebbero persi (con la conseguenza che <=> potrebbe arbitrariamente finire con qualsiasi valore compreso tra 1 e 20.

Oggetti / Structs sono immutabili quando questi vengono passati a una funzione in modo tale che i dati non possono essere cambiati, e la struttura restituita è una struct new. L'esempio classico è

String s = "abc";

s.toLower();

se la funzione toLower è scritto in modo che una nuova stringa viene restituita che sostituisce "s", è immutabile, ma se la funzione va lettera per lettera che sostituisce la lettera dentro "s" e mai dichiarare un "new String" , è mutevole.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top