2012-04-06 24 views
13

La domanda principale è quali sono le implicazioni di consentire che questa parola chiave venga modificata in termini di utilità e memoria; e perché è consentito nelle specifiche del linguaggio C#?Assegnare questa parola chiave in C#

Le altre domande/sottoparti possono essere risposte o meno se si sceglie di farlo. Pensavo che le risposte a loro avrebbero aiutato a chiarire la risposta alla domanda principale.

mi sono imbattuto in questo come una risposta a What's the strangest corner case you've seen in C# or .NET?

public struct Teaser 
{ 
    public void Foo() 
    { 
     this = new Teaser(); 
    } 
} 

Ho cercato di avvolgere la mia testa intorno perché le specifiche del linguaggio C# sarebbe nemmeno permettere questo. Sottotitolo 1. c'è qualcosa che giustificherebbe che questo sia modificabile? È tutto utile?

Uno dei commenti a questa risposta era

Da CLR via C#: La ragione per cui hanno fatto questo è perché si possibile chiamare il costruttore senza parametri di una struttura in un altro costruttore. Se vuoi solo inizializzare un valore di una struct e vuoi che gli altri valori siano zero/null (default), puoi scrivere public Foo (int bar) {this = new Foo(); specialVar = bar;}. Questo non è efficiente e non molto giustificato (specialeVar viene assegnato due volte), ma solo FYI. (Questo è il motivo addotto nel libro, non so il motivo per cui non dovremmo solo fare pubblica Foo (int bar): questo())

Sotto-parte 2. io non sono certo seguo questo ragionamento Qualcuno può chiarire cosa intendeva? Forse un esempio concreto di come sarebbe usato?

EDIT (Ignora stack o heap main point è relativo alla liberazione di memoria o alla garbage collection. Invece di int [] è possibile sostituirlo con 262144 campi pubblici int) Anche dalle mie strutture di comprensione vengono create nello stack come opposto al mucchio se questa struct dovesse avere un campo matrice di byte 1 Mb inizializzato come così

public int[] Mb = new int[262144]; 

Sotto-parte 3. vuol mai vengono rimossi dallo stack quando viene chiamato Foo? A me sembra che, dal momento che la struttura non è mai stata fuori portata, non sarebbe stata rimossa dallo stack. Non ho tempo stasera per creare un caso di prova, ma forse lo farò domani.

Nel codice qui sotto

Teaser t1 = new Teaser(); 
Teaser tPlaceHolder = t1; 
t1.Foo(); 

Sotto-parte 4. T1 e tPlaceHolder occupano lo stesso spazio indirizzo o diverso?

Mi spiace far apparire un post di 3 anni, ma questo mi ha davvero graffiato la testa.

FYI prima domanda su stackoverflow quindi se ho qualcosa di sbagliato con la domanda gentilmente postare un commento e modifico.

Dopo 2 giorni metterò una taglia di 50 su questa domanda, anche se ho già scelto un vincitore nella mia mente come penso che la risposta richiederà una quantità ragionevole di lavoro per spiegare le domande.

+3

4 domande in 1 non sono appropriate per Stack Overflow. Una domanda * dovrebbe essere una domanda. * –

+4

@AnthonyPegram - In questo caso penso sia accettabile dal momento che queste domande sono domande secondarie rispetto alla domanda principale, ovvero come funzionano veramente le strutture – Polity

+1

Le mie scuse non sono sicuro di come riformulare tutto in una sola. La domanda principale, suppongo, può essere riassunta da quali sono gli effetti collaterali di consentire la modifica di questa parola chiave? Le domande 3-4 sono effetti collaterali che pensavo potessero essere una possibilità. Se qualcuno ha il potere di modificare la domanda oltre a me e pensa di poterlo chiedere meglio, sentiti libero di farlo. Oppure lascia un commento suggerendo una domanda migliore e inizierò da zero. –

risposta

6

Prima di tutto, penso che dovresti iniziare esaminando se stai anche facendo la domanda giusta. Forse dovremmo chiederci: "Perché C# non consentire l'assegnazione a this in una struttura?"

L'assegnazione alla parola chiave this in un tipo di riferimento è potenzialmente pericolosa: si sta sovrascrivendo un riferimento all'oggetto, il metodo che si sta eseguendo, si potrebbe anche farlo all'interno del costruttore che sta inizializzando tale riferimento. Per evitare di doverlo capire, poiché non è generalmente utile, non è consentito dalla specifica (o dal compilatore)

Assegnare alla parola chiave this in un tipo di valore, tuttavia , è ben definito L'assegnazione dei tipi di valore è un'operazione di copia Il valore di ciascun campo viene copiato ricorsivamente da destra a sinistra dell'assegnazione.Questa è un'operazione perfettamente sicura su una struttura, anche in un costruttore, perché e la copia originale della struttura è ancora presente, stai solo cambiando i suoi dati. È esattamente equivalente all'impostazione manuale di ciascun campo nella struttura. Perché le specifiche o il compilatore dovrebbero proibire un'operazione che sia ben definita e sicura?

Questo, a proposito, risponde a una delle vostre sotto-domande. L'assegnazione del tipo di valore è un'operazione di copia profonda, non una copia di riferimento. Dato questo codice:

Teaser t1 = new Teaser(); 
Teaser tPlaceHolder = t1; 
t1.Foo(); 

Hai assegnato due copie del vostro Teaser struttura e copiato i valori dei campi nella prima nei campi nel secondo. Questa è la natura dei tipi di valore: due tipi che hanno campi identici sono identici, proprio come due variabili int che contengono entrambi 10 sono identici, indipendentemente da dove sono "in memoria".

Inoltre, questo è importante e vale la pena ripetere: fare ipotesi accurate su ciò che accade "lo stack" rispetto a "the heap". I tipi di valore finiscono sullo heap in ogni momento, a seconda del contesto in cui vengono utilizzati. È probabile che le strutture di breve durata (localmente con scope) non chiuse o altrimenti rimosse dal loro ambito siano allocate nello stack. Ma questo è un senza importanza implementation detail che non dovresti preoccuparti né fidarti. La chiave è che sono tipi di valore e si comportano come tali.

Per quanto utile è l'assegnazione a this: non molto. Casi d'uso specifici sono già stati menzionati. È possibile utilizzarlo per inizializzare principalmente una struttura con valori predefiniti, ma specificare un numero piccolo. Dal momento che si sono tenuti a impostare tutti i campi prima di vostre dichiarazioni del costruttore, questo può risparmiare un sacco di codice ridondante:

public struct Foo 
{ 
    // Fields etc here. 

    public Foo(int a) 
    { 
    this = new Foo(); 
    this.a = a; 
    } 
} 

Può essere utilizzato anche per eseguire un'operazione di swap rapida:

public void SwapValues(MyStruct other) 
{ 
    var temp = other; 
    other = this; 
    this = temp; 
} 

Oltre a ciò , è solo un interessante effetto collaterale della lingua e il modo in cui sono implementate le strutture e i tipi di valore che probabilmente non avrai mai bisogno di sapere.

+0

+1 per tentare prima che fosse chiuso. Cercherò di riformulare questa domanda prima o poi domani. –

+0

Ho deciso di votare questa è la risposta e non riaprirene una nuova. La copia in base al valore è ciò che ha fatto affondare definitivamente. Immagino nella mia mente che ho sempre pensato che "questo" fosse di riferimento e non pensavo in termini di "questo" come riferito al tipo di valore quando si trattava di strutture. Non penso nemmeno di usare questa parola chiave in nessuna delle strutture che ho creato. –

1

Avere questo assegnabile consente casi di angoli 'avanzati' con le strutture. Un esempio che ho trovato è stato un metodo di scambio:

struct Foo 
{ 
    void Swap(ref Foo other) 
    { 
     Foo temp = this; 
     this = other; 
     other = temp; 
    } 
} 

caldamente argomentare contro questo uso in quanto viola il default 'desiderata' la natura di una struttura che è immutabilità. La ragione per avere questa opzione è probabilmente poco chiara.

Ora quando si tratta di strutturarsi. Differiscono dalle classi in alcuni modi:

  • Possono vivere sullo stack anziché l'heap gestito.
  • È possibile eseguire il marshalling su codice non gestito.
  • Non possono essere assegnati a un valore NULL.

Per una panoramica completa, si veda: http://www.jaggersoft.com/pubs/StructsVsClasses.htm

relativa alla sua domanda è se la tua struct vive sullo stack o heap. Questo è determinato dalla posizione di allocazione di una struttura. Se la struct è un membro di una classe, verrà allocata nell'heap. Altrimenti se una struct è allocata direttamente, sarà allocata nell'heap (in realtà questa è solo una parte dell'immagine.) Questo insieme diventerà piuttosto complesso una volta che inizieremo a parlare di chiusure introdotte in C# 2.0 ma per ora è sufficiente per rispondi alla tua domanda).

Un array in .NET è il valore predefinito allocato sull'heap (questo comportamento non è coerente quando si utilizza il codice non sicuro e la parola chiave stackalloc). Tornando alla spiegazione sopra, ciò indicherebbe che anche le istanze della struct vengono allocate nell'heap. In effetti, un modo semplice per dimostrarlo è allocare un array di dimensioni pari a 1 MB e osservare il modo in cui NON viene generata alcuna eccezione stackoverflow.

La durata per un'istanza nello stack è determinata dal suo ambito. Questo è diverso da un'istanza nell'heap del gestore che è determinata dal garbage collector (e se ci sono ancora riferimenti a quell'istanza). Puoi assicurarti che tutto ciò che si trova nello stack duri finché è all'interno dello scope. Assegnando un'istanza nello stack e chiamando un metodo, non rilasciare quell'istanza fino a quando quell'istanza non viene portata fuori dall'ambito (per impostazione predefinita, quando il metodo in cui tale istanza è stata dichiarata termina).

Una struttura non ha gestito riferimenti verso di esso (i puntatori sono possibili nel codice non gestito). Quando lavori con le strutture nello stack in C#, hai fondamentalmente un'etichetta verso un'istanza piuttosto che un riferimento. Assegnare una struttura a un'altra copia semplicemente i dati sottostanti. Puoi vedere i riferimenti come strutture. Messo in modo ingannevole, un riferimento non è altro che una struttura contenente un puntatore a una determinata parte in memoria. Quando si assegna un riferimento all'altro, i dati del puntatore vengono copiati.

// declare 2 references to instances on the managed heap 
var c1 = new MyClass(); 
var c2 = new MyClass(); 

// declare 2 labels to instances on the stack 
var s1 = new MyStruct(); 
var s2 = new MyStruct(); 

c1 = c2; // copies the reference data which is the pointer internally, c1 and c2 both point to the same instance 
s1 = s2; // copies the data which is the struct internally, c1 and c2 both point to their own instance with the same data 
+0

Suppongo che sia stato copiato nello stack o al heap quando si chiama Foo il GC pulirà la memoria associata alla struttura originale? Giusto per chiarire che sto parlando di codice strettamente gestito. Ohh e di Foo intendevo il Foo nella struttura del Teaser, non la tua struttura di Foo. –

+0

Il GC pulirà solo le istanze allocate nell'heap gestito. Le istanze basate sullo stack non vengono realmente ripulite (quindi, una struttura non può contenere un distruttore). La loro etichetta diventerà semplicemente non valida una volta fuori dal campo di applicazione. Vedi la risposta di eric lippert in questo post: http://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope – Polity

+0

le strutture sono tipi di valore; non vengono mai raccolti in modo esplicito. La memoria per i tipi di valore viene restituita al sistema operativo quando il tipo di valore lascia l'ambito; se si tratta di una variabile locale o di un parametro, ciò accade e la funzione restituisce. Se è un campo, succede quando la sua classe contenitore è GC'd. Etc. –