Domanda

mutabili le strutture sono il male in generale. Sono anche abbastanza sicuro che, poiché i rendimenti IEnumerable<T>.GetEnumerator() tipo IEnumerator<T>, le struct sono immediatamente inscatolati in un tipo di riferimento, un costo superiore se fossero semplicemente i tipi di riferimento per cominciare.

E allora perché, nel BCL collezioni generici, sono tutti gli enumeratori le strutture mutevoli? Sicuramente ci doveva essere stato un buon motivo. L'unica cosa che viene in mente è che le strutture possono essere copiati facilmente, preservando in tal modo lo stato enumeratore in un punto arbitrario. Ma l'aggiunta di un metodo Copy() all'interfaccia IEnumerator sarebbe stato meno fastidioso, quindi non vedo questo come una giustificazione logica a sé stante.

Anche se non sono d'accordo con una decisione di progettazione, vorrei essere in grado di capire il ragionamento dietro di esso.

È stato utile?

Soluzione

In effetti, è per motivi di prestazioni. Il team ha fatto un BCL molto di ricerca su questo punto prima di decidere di andare con quello che giustamente chiami come una pratica sospetta e pericolosa:. L'uso di un tipo di valore mutabile

Mi chiedi perché questo non causa la boxe. È perché il compilatore C # non genera il codice a scatola roba da IEnumerable o IEnumerator in un ciclo foreach se può evitarlo!

Quando vediamo

foreach(X x in c)

La prima cosa che facciamo è controllare per vedere se c è un metodo chiamato GetEnumerator. Se è così, allora controlliamo per vedere se il tipo restituisce ha metodo MoveNext e la corrente di proprietà. Se è così, allora il ciclo foreach viene generato interamente con chiamate dirette di detti metodi e le proprietà. Solo se "il modello" non può essere eguagliata fare ricadiamo a guardare per le interfacce.

Questo ha due effetti desiderabili.

In primo luogo, se la raccolta è, diciamo, una raccolta di int, ma è stato scritto prima tipi generici sono stati inventati, allora non ci vuole la pena di pugilato della Boxe il valore della corrente di oggetto e poi unboxing a int. Se Current è una proprietà che restituisce un int, che abbiamo appena lo utilizzano.

In secondo luogo, se l'enumeratore è un tipo di valore allora non box l'enumeratore IEnumerator.

Come ho detto, il team BCL ha fatto un sacco di ricerche su questo e ha scoperto che la maggior parte del tempo, la pena di allocare e deallocando l'enumeratore è abbastanza grande che era la pena di fare è un tipo di valore, anche se così facendo può causare alcuni bug folli.

Per esempio, si consideri questo:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h = somethingElse;
}

Si potrebbe giustamente si aspettano il tentativo di mutare h di fallire, e in effetti lo fa. I rileva compilatore che si sta tentando di modificare il valore di qualcosa che ha una cessione incombente, e che così facendo potrebbero causare l'oggetto che deve essere disposti a realtà non essere smaltito.

Ora si supponga di avere:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h.Mutate();
}

Che cosa succede qui? Si potrebbe ragionevolmente aspettarsi che il compilatore sarebbe fare quello che fa, se h fosse un campo di sola lettura: fare una copia, e mutare la copia al fine di garantire che il metodo non buttare via roba di valore che deve essere smaltito.

Tuttavia, in conflitto con la nostra intuizione di quello che dovrebbe accadere qui:

using (Enumerator enumtor = whatever)
{
    ...
    enumtor.MoveNext();
    ...
}

Ci aspettiamo che facendo un MoveNext all'interno di un blocco utilizzando si spostare l'enumeratore a quello successivo indipendentemente dal fatto che si tratta di una struttura o di un tipo rif.

Purtroppo, il compilatore C # oggi ha un bug. Se siete in questa situazione abbiamo scelto quale strategia da seguire in modo incoerente. Il comportamento di oggi è:

  • se la variabile valore tipizzato essendo mutata tramite un metodo è un normale locale allora è mutato normalmente

  • ma se è un locale issata (perché è una variabile chiuso su di una funzione anonima o in un blocco iteratore) allora il locale è effettivamente generato come un campo di sola lettura , e la marcia che assicura che le mutazioni avvengono su una copia prende il sopravvento.

Purtroppo la specifica fornisce poche indicazioni in materia. Evidentemente qualcosa è rotto perché lo stiamo facendo in modo incoerente, ma quello che il destro cosa da fare non è affatto chiaro.

Altri suggerimenti

Metodi Struct sono inline quando il tipo di struct è noto al momento della compilazione, e il metodo chiamando tramite interfaccia è lento, quindi risposta è:. A causa della ragione prestazioni

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