L'immutabilità è molto utile quando non c'è concorrenza?


53

Sembra che la sicurezza dei thread sia sempre / spesso menzionata come il principale vantaggio dell'utilizzo di tipi immutabili e in particolare delle raccolte.

Ho una situazione in cui vorrei assicurarmi che un metodo non modifichi un dizionario di stringhe (che sono immutabili in C #). Mi piacerebbe limitare le cose il più possibile.

Tuttavia, non sono sicuro che valga la pena aggiungere una dipendenza a un nuovo pacchetto (Microsoft Immutable Collections). Anche le prestazioni non sono un grosso problema.

Quindi suppongo che la mia domanda sia se le collezioni immutabili sono fortemente consigliate quando non ci sono requisiti di prestazioni difficili e non ci sono problemi di sicurezza del thread? Considera che la semantica del valore (come nel mio esempio) potrebbe o non potrebbe essere un requisito.


1
Modifiche simultanee non devono necessariamente significare discussioni. Basta guardare il nome appropriato ConcurrentModificationExceptionche di solito è causato dallo stesso thread che muta la raccolta nello stesso thread, nel corpo di un foreachciclo sulla stessa collezione.

1
Voglio dire che non sbagli, ma è diverso da quello che sta chiedendo OP. Tale eccezione viene generata perché non sono consentite modifiche durante l'enumerazione. L'uso di un ConcurrentDictionary, ad esempio, avrebbe comunque questo errore.
edthirdird

13
Forse dovresti anche porti la domanda opposta: quando vale la mutabilità?
Giorgio,

2
In Java, se la mutabilità influisce hashCode()o il equals(Object)risultato viene modificato, può causare errori durante l'utilizzo Collections(ad esempio, in un HashSetoggetto è stato memorizzato in un "secchio" e dopo la mutazione dovrebbe passare a un altro).
SJuan76,

2
@ DavorŽdralo Per quanto riguarda le astrazioni delle lingue di alto livello, l'immutabilità pervasiva è piuttosto docile. È solo una naturale estensione dell'astrazione molto comune (presente anche in C) di creare e scartare silenziosamente "valori temporanei". Forse intendi dire che è un modo inefficiente di usare una CPU, ma anche quell'argomento ha dei difetti: i linguaggi felici della mutabilità ma spesso funzionano meglio dei linguaggi solo immutabili ma statici, in parte perché ci sono alcuni intelligenti (ma alla fine abbastanza semplici) trucchi per ottimizzare i programmi che manipolano dati immutabili: tipi lineari, deforestazione, ecc.

Risposte:


101

L'immutabilità semplifica la quantità di informazioni necessarie per tracciare mentalmente durante la lettura del codice in un secondo momento . Per le variabili mutabili, e in particolare per i membri della classe mutabili, è molto difficile sapere in quale stato si troveranno nella riga specifica di cui stai leggendo, senza scorrere il codice con un debugger. I dati immutabili sono facili da ragionare : saranno sempre gli stessi. Se vuoi cambiarlo, devi creare un nuovo valore.

Preferirei onestamente rendere le cose immutabili per impostazione predefinita , e poi cambiarle in mutabili dove è dimostrato che devono essere, se questo significa che hai bisogno delle prestazioni o un algoritmo che hai non ha senso per l'immutabilità.


23
+1 La concorrenza è una mutazione simultanea, ma la mutazione che si diffonde nel tempo può essere altrettanto difficile da ragionare
guillaume31

20
Per espandere questo: una funzione che si basa su una variabile mutabile può essere considerata come prendere un argomento nascosto in più, che è il valore corrente della variabile mutabile. Qualsiasi funzione che modifica una variabile mutabile può anche essere considerata come produrre un valore di ritorno extra, cioè il nuovo valore dello stato mutabile. Quando guardi un pezzo di codice non hai idea se dipende o cambia stato mutabile, quindi devi scoprire e quindi tenere traccia dei cambiamenti mentalmente. Ciò introduce anche l'accoppiamento tra due parti di codice che condividono uno stato mutabile e l'accoppiamento è errato.
Doval,

29
@Mehrdad Le persone sono anche riuscite a mettere in piedi grandi programmi in assemblea per decenni. Poi abbiamo fatto un paio di decenni di C.
Doval,

11
@Mehrdad La copia di interi oggetti non è una buona opzione quando gli oggetti sono grandi. Non vedo perché l'ordine di grandezza coinvolto nel miglioramento sia importante. Rifiuteresti un miglioramento del 20% (nota: numero arbitrario) della produttività semplicemente perché non era un miglioramento a tre cifre? L'immutabilità è un default sano ; si può allontanarsi da esso, ma è necessario un motivo.
Doval,

9
@Giorgio Scala mi ha fatto capire quanto di rado sia necessario rendere un valore mutevole. Ogni volta che uso quella lingua, faccio tutto a val, e solo in occasioni molto, molto rare trovo che devo cambiare qualcosa in a var. Molte delle "variabili" che definisco in una determinata lingua sono proprio lì per contenere un valore che memorizza il risultato di un certo calcolo e non ha bisogno di essere aggiornato.
KChaloux,

22

Il tuo codice dovrebbe esprimere la tua intenzione. Se non si desidera che un oggetto venga modificato una volta creato, rendere impossibile la modifica.

L'immutabilità ha diversi vantaggi:

  • L'intenzione dell'autore originale è espressa meglio.

    Come potresti sapere che, nel seguente codice, la modifica del nome provocherebbe la generazione di un'eccezione da qualche parte in seguito?

    public class Product
    {
        public string Name { get; set; }
    
        ...
    }
    
  • È più facile assicurarsi che l'oggetto non appaia in uno stato non valido.

    Devi controllarlo in un costruttore, e solo lì. D'altra parte, se si dispone di un gruppo di setter e metodi che modificano l'oggetto, tali controlli possono diventare particolarmente difficili, specialmente quando, ad esempio, due campi devono cambiare contemporaneamente affinché l'oggetto sia valido.

    Ad esempio, un oggetto è valido se l'indirizzo non lo è null o le coordinate GPS non lo sono null, ma non è valido se vengono specificati sia l'indirizzo che le coordinate GPS. Riesci a immaginare l'inferno di convalidare questo se l'indirizzo e le coordinate GPS hanno un setter o entrambi sono mutabili?

  • Concorrenza.

A proposito, nel tuo caso, non hai bisogno di pacchetti di terze parti. .NET Framework include già una ReadOnlyDictionary<TKey, TValue>classe.


1
+1, in particolare per "Devi controllarlo in un costruttore, e solo lì." IMO questo è un enorme vantaggio.
Giorgio,

10
Un altro vantaggio: copiare un oggetto è gratuito. Solo un puntatore.
Robert Grant,

1
@MainMa Grazie per la risposta, ma per quanto ho capito ReadOnlyDictionary non fornisce alcuna garanzia che qualcun altro non cambierà il dizionario sottostante (anche senza concorrenza potrei voler salvare il riferimento al dizionario originale all'interno dell'oggetto al quale appartiene il metodo per un uso successivo). ReadOnlyDictionary viene persino dichiarato in uno strano spazio dei nomi: System.Collections.ObjectModel.
Den

2
@Den: Questo riguarda uno dei miei animali domestici: le persone che considerano "sola lettura" e "immutabile" come sinonimi. Se un oggetto è incapsulato in un wrapper di sola lettura e non esiste alcun altro riferimento o è conservato in qualsiasi punto dell'universo, il wrapping dell'oggetto lo renderà immutabile e un riferimento al wrapper può essere utilizzato come abbreviazione per incapsulare lo stato del oggetto in esso contenuto. Tuttavia, non esiste alcun meccanismo attraverso il quale il codice possa accertare se sia così. Al contrario, poiché il wrapper nasconde il tipo di oggetto avvolto, avvolgendo un oggetto immutabile ...
supercat

2
... renderà impossibile al codice sapere se il wrapper risultante può essere tranquillamente considerato immutabile.
supercat

13

Esistono molte ragioni a thread singolo per utilizzare l'immutabilità. Per esempio

L'oggetto A contiene l'oggetto B.

Il codice esterno richiede l'oggetto B e lo si restituisce.

Ora hai tre possibili situazioni:

  1. B è immutabile, nessun problema.
  2. B è mutabile, fai una copia difensiva e la restituisci. Colpo di prestazione ma nessun rischio.
  3. B è mutevole, tu lo restituisci.

Nel terzo caso, il codice utente potrebbe non rendersi conto di ciò che hai fatto e apportare modifiche all'oggetto, e in tal modo modificare i dati interni del tuo oggetto senza che tu abbia alcun controllo o visibilità su ciò che accade.


9

L'immutabilità può anche semplificare notevolmente l'implementazione dei netturbini. Dal wiki di GHC :

[...] L'immutabilità dei dati ci obbliga a produrre molti dati temporanei, ma aiuta anche a raccogliere rapidamente questi rifiuti. Il trucco è che i dati immutabili non puntano MAI a valori più giovani. In effetti, i valori più giovani non esistono ancora nel momento in cui viene creato un vecchio valore, quindi non può essere indicato da zero. E poiché i valori non vengono mai modificati, non può essere indicato in un secondo momento. Questa è la proprietà chiave dei dati immutabili.

Questo semplifica enormemente la garbage collection (GC). In qualsiasi momento possiamo scansionare gli ultimi valori creati e liberare quelli che non sono indicati dallo stesso set (ovviamente, le radici reali della gerarchia dei valori attivi sono attive nello stack). [...] Quindi ha un comportamento controintuitivo: maggiore è la percentuale di valori immondizia, più veloce funziona. [...]


5

Espandendo ciò che KChaloux ha riassunto molto bene ...

Idealmente, hai due tipi di campi e quindi due tipi di codice che li utilizzano. Entrambi i campi sono immutabili e il codice non deve tenere conto della mutabilità; oppure i campi sono mutabili e dobbiamo scrivere codice che esegua uno snapshot ( int x = p.x) o gestisca con garbo tali cambiamenti.

Nella mia esperienza, la maggior parte del codice si colloca tra i due, essendo un codice ottimista : fa liberamente riferimento a dati mutabili, supponendo che la prima chiamata p.xabbia lo stesso risultato della seconda chiamata. E il più delle volte, questo è vero, tranne quando si scopre che non lo è più. Ops.

Quindi, in realtà, rivolgi questa domanda: quali sono le mie ragioni per rendere questo mutevole ?

  • Riduzione dell'allocazione / libera della memoria?
  • Mutevole per natura? (es. contatore)
  • Salva modificatori, rumore orizzontale? (Const / def)
  • Rende il codice più breve / più semplice? (init default, eventualmente sovrascrivere dopo)

Scrivi un codice difensivo? L'immutabilità ti risparmierà qualche copia in giro. Scrivi un codice ottimista? L'immutabilità ti risparmierà la follia di quel bizzarro, impossibile bug.


3

Un altro vantaggio dell'immutabilità è che è il primo passo per arrotondare questi oggetti immutabili in un pool. Quindi puoi gestirli in modo da non creare più oggetti che concettualmente e semanticamente rappresentano la stessa cosa. Un buon esempio potrebbe essere la stringa di Java.

È un fenomeno ben noto in campo linguistico che alcune parole appaiono molto, potrebbero apparire anche in altri contesti. Quindi, invece di creare più Stringoggetti, puoi usarne uno immutabile. Ma poi è necessario mantenere un gestore di pool per prendersi cura di questi oggetti immutabili.

Questo ti farà risparmiare molta memoria. Anche questo è un articolo interessante da leggere: http://en.wikipedia.org/wiki/Zipf%27s_law


1

In Java, C # e altri linguaggi simili, i campi di tipo classe possono essere utilizzati per identificare oggetti o per incapsulare valori o stati in tali oggetti, ma i linguaggi non fanno distinzioni tra tali usi. Supponiamo che un oggetto classe Georgeabbia un campo di tipo char[] chars;. Tale campo può incapsulare una sequenza di caratteri in uno dei seguenti modi:

  1. Un array che non verrà mai modificato, né esposto a nessun codice che potrebbe modificarlo, ma al quale potrebbero esistere riferimenti esterni.

  2. Un array in cui non esistono riferimenti esterni, ma che George può modificare liberamente.

  3. Un array di proprietà di George, ma al quale potrebbero esistere punti di vista esterni che potrebbero rappresentare lo stato attuale di George.

Inoltre, la variabile può, invece di incapsulare una sequenza di caratteri, incapsulare una vista dal vivo a una sequenza di caratteri di proprietà di qualche altro oggetto

Se charsattualmente incapsula la sequenza di caratteri [vento] e George vuole charsincapsulare la sequenza di caratteri [bacchetta], ci sono un certo numero di cose che George potrebbe fare:

A. Costruisci un nuovo array contenente i caratteri [bacchetta] e modifica charsper identificare quell'array anziché quello vecchio.

B. Identifica in qualche modo una matrice di caratteri preesistente che conterrà sempre i caratteri [bacchetta] e cambierà charsper identificare quella matrice piuttosto che quella vecchia.

C. Modificare il secondo carattere dell'array identificato da charsin un a.

Nel caso 1, (A) e (B) sono modi sicuri per ottenere il risultato desiderato. Nel caso in cui (2), (A) e (C) siano sicuri, ma (B) non sarebbe [non causerebbe problemi immediati, ma dal momento che George presumerebbe che fosse proprietario dell'array, supponerebbe che potrebbe cambiare l'array a piacimento]. Nel caso (3), le scelte (A) e (B) infrangerebbero qualsiasi vista esterna, e quindi solo la scelta (C) è corretta. Pertanto, sapere come modificare la sequenza di caratteri incapsulata dal campo richiede sapere quale tipo di campo semantico è.

Se invece di utilizzare un campo di tipo char[], che incapsula una sequenza di caratteri potenzialmente mutabile, il codice ha usato il tipo String, che incapsula una sequenza di caratteri immutabile, tutti i problemi sopra descritti scompaiono. Tutti i campi di tipo Stringincapsulano una sequenza di caratteri usando un oggetto condivisibile che non cambierà mai. Di conseguenza, se un campo di tipoStringincapsula "vento", l'unico modo per farlo incapsulare "bacchetta" è fargli identificare un oggetto diverso - uno che contiene "bacchetta". Nei casi in cui il codice contiene l'unico riferimento all'oggetto, la mutazione dell'oggetto può essere più efficiente della creazione di uno nuovo, ma ogni volta che una classe è mutabile è necessario distinguere tra i diversi modi in cui può incapsulare il valore. Personalmente penso che le app ungheresi avrebbero dovuto essere usate per questo (considererei i quattro usi char[]come tipi semanticamente distinti, anche se il sistema dei tipi li considera identici - esattamente il tipo di situazione in cui brilla app ungherese), ma dal momento che non era il modo più semplice per evitare tali ambiguità è progettare tipi immutabili che incapsulino i valori solo in un modo.


Sembra una risposta ragionevole, ma è un po 'difficile da leggere e comprendere.
Den

1

Ci sono alcuni esempi interessanti qui, ma ho voluto entrare con alcuni personali in cui l'immutabilità ha aiutato moltissimo. Nel mio caso, ho iniziato a progettare una struttura di dati simultanea immutabile principalmente con la speranza di poter semplicemente eseguire il codice in modo sicuro in parallelo con letture e scritture sovrapposte e senza doversi preoccupare delle condizioni di gara. C'è stato un discorso in cui John Carmack mi ha ispirato a farlo quando ha parlato di una simile idea. È una struttura piuttosto semplice e piuttosto banale da implementare in questo modo:

inserisci qui la descrizione dell'immagine

Ovviamente con qualche altro campanello e fischietto ad esso come essere in grado di rimuovere elementi in tempo costante e lasciare dietro di sé buchi recuperabili e fare in modo che i blocchi vengano eliminati se si svuotano e potenzialmente si liberano per una data istanza immutabile. Ma fondamentalmente per modificare la struttura, si modifica una versione "transitoria" e si impegnano atomicamente le modifiche apportate ad essa per ottenere una nuova copia immutabile che non tocca quella vecchia, con la nuova versione che crea solo nuove copie dei blocchi che devono essere resi unici durante la copia superficiale e il conteggio degli altri.

Tuttavia, non ho trovato è cheutile per scopi di multithreading. Dopotutto, c'è ancora il problema concettuale in cui, per esempio, un sistema fisico applica la fisica contemporaneamente mentre un giocatore sta cercando di spostare elementi in un mondo. Con quale copia immutabile dei dati trasformati vai, quella trasformata dal giocatore o quella trasformata dal sistema fisico? Quindi non ho davvero trovato una soluzione piacevole e semplice a questo problema concettuale di base se non quello di avere strutture di dati mutabili che si bloccano in un modo più intelligente e scoraggiano le letture e le scritture sovrapposte nelle stesse sezioni del buffer per evitare di bloccare i thread. Questo è qualcosa che John Carmack sembra aver probabilmente capito come risolvere nei suoi giochi; almeno ne parla come se potesse quasi vedere una soluzione senza aprire un'auto di vermi. Non sono arrivato fino a lui in questo senso. Tutto quello che posso vedere sono infinite domande sul design se provassi a parallelizzare tutto intorno a immutabili. Vorrei poter passare una giornata a raccogliere il cervello dal momento che la maggior parte dei miei sforzi è iniziata con quelle idee che ha buttato fuori.

Tuttavia, ho trovato un valore enorme di questa struttura di dati immutabile in altre aree. Lo uso persino ora per archiviare immagini che sono davvero strane e che richiedono un accesso casuale richiedono più istruzioni (spostamento a destra e bit a bit andcon uno strato di indiretto del puntatore), ma tratterò i vantaggi di seguito.

Annulla sistema

Uno dei posti più immediati che ho trovato per trarne vantaggio è stato il sistema di annullamento. Il codice di sistema di annullamento era una delle cose più soggette a errori nella mia area (settore degli effetti visivi) e non solo nei prodotti su cui ho lavorato ma in prodotti concorrenti (anche i loro sistemi di annullamento erano traballanti) perché c'erano così tanti diversi tipi di dati di cui preoccuparsi per l'annullamento e la ripetizione corretta (sistema di proprietà, modifiche dei dati mesh, cambiamenti dello shader che non erano basati su proprietà come lo scambio l'uno con l'altro, la gerarchia delle scene cambia come la modifica del genitore di un bambino, i cambiamenti di immagine / trama, ecc. ecc. ecc.).

Quindi la quantità di codice di annullamento richiesta era enorme, spesso in concorrenza con la quantità di codice che implementava il sistema per il quale il sistema di annullamento doveva registrare i cambiamenti di stato. Appoggiandosi a questa struttura di dati, sono stato in grado di ottenere il sistema di annullamento fino a questo:

on user operation:
    copy entire application state to undo entry
    perform operation

on undo/redo:
    swap application state with undo entry

Normalmente quel codice sopra sarebbe enormemente inefficiente quando i dati della scena si estendono su gigabyte per copiarlo interamente. Ma questa struttura di dati copia solo superficialmente cose che non sono state modificate, e in realtà lo ha reso abbastanza economico archiviare una copia immutabile dell'intero stato dell'applicazione. Quindi ora posso implementare i sistemi di annullamento con la stessa facilità del codice sopra e concentrarmi solo sull'utilizzo di questa struttura di dati immutabile per rendere la copia di parti invariate dello stato dell'applicazione sempre più economica. Da quando ho iniziato a utilizzare questa struttura di dati, tutti i miei progetti personali hanno sistemi di annullamento usando solo questo semplice schema.

Ora c'è ancora un po 'di spese generali qui. L'ultima volta che ho misurato erano circa 10 kilobyte solo per copiare superficialmente l'intero stato dell'applicazione senza apportare modifiche ad esso (questo è indipendente dalla complessità della scena poiché la scena è disposta in una gerarchia, quindi se nulla al di sotto della radice cambia, solo la radice è poco profondo copiato senza dover scendere nei bambini). È lontano da 0 byte come sarebbe necessario per un sistema di annullamento che memorizza solo i delta. Ma a 10 kilobyte di overhead di annullamento per operazione, è ancora solo un megabyte per 100 operazioni utente. Inoltre potrei ancora potenzialmente comprimerlo ulteriormente in futuro, se necessario.

Eccezione-sicurezza

La sicurezza delle eccezioni con un'applicazione complessa non è cosa da poco. Tuttavia, quando lo stato dell'applicazione è immutabile e si utilizzano solo oggetti transitori per tentare di eseguire il commit di transazioni di cambiamento atomico, è intrinsecamente sicuro dalle eccezioni poiché se viene lanciata una parte del codice, il transitorio viene gettato via prima di dare una nuova copia immutabile . In modo che banalizzi una delle cose più difficili che ho sempre trovato per ottenere proprio in una complessa base di codice C ++.

Troppe persone spesso usano solo risorse conformi alla RAII in C ++ e pensano che sia abbastanza sicuro per le eccezioni. Spesso non lo è, dal momento che una funzione può generalmente causare effetti collaterali a stati oltre quelli locali al suo ambito. In questi casi in genere è necessario iniziare a occuparsi delle protezioni dell'ambito e della sofisticata logica di rollback. Questa struttura di dati lo ha reso quindi spesso non ho bisogno di preoccuparmene poiché le funzioni non causano effetti collaterali. Stanno restituendo copie immutabili trasformate dello stato dell'applicazione invece di trasformare lo stato dell'applicazione.

Editing non distruttivo

inserisci qui la descrizione dell'immagine

L'editing non distruttivo è fondamentalmente operazioni di stratificazione / impilamento / connessione senza toccare i dati dell'utente originale (basta inserire i dati e i dati di output senza toccare l'input). In genere è banale implementarlo con una semplice applicazione di immagine come Photoshop e potrebbe non trarre grandi benefici da questa struttura di dati poiché molte operazioni potrebbero semplicemente voler trasformare ogni pixel dell'intera immagine.

Tuttavia, con la modifica mesh non distruttiva, ad esempio, molte operazioni spesso vogliono trasformare solo una parte della mesh. Un'operazione potrebbe voler solo spostare alcuni vertici qui. Un altro potrebbe semplicemente voler suddividere alcuni poligoni lì. Qui la struttura dei dati immutabile aiuta moltissimo a evitare la necessità di fare un'intera copia dell'intera mesh solo per restituire una nuova versione della mesh con una piccola parte di essa modificata.

Riduzione al minimo degli effetti collaterali

Con queste strutture a portata di mano, semplifica anche la scrittura di funzioni che minimizzano gli effetti collaterali senza incorrere in un'enorme penalità per le prestazioni. Mi sono ritrovato a scrivere sempre più funzioni che al giorno d'oggi restituiscono strutture di dati intere immutabili per valore senza incorrere in effetti collaterali, anche quando ciò sembra un po 'dispendioso.

Ad esempio, in genere la tentazione di trasformare un gruppo di posizioni potrebbe essere quella di accettare una matrice e un elenco di oggetti e trasformarli in modo mutevole. In questi giorni mi ritrovo a restituire un nuovo elenco di oggetti.

Quando hai più funzioni come questa nel tuo sistema che non causano effetti collaterali, rende sicuramente più facile ragionare sulla sua correttezza e testarne la correttezza.

I vantaggi delle copie economiche

Ad ogni modo, queste sono le aree in cui ho trovato il maggior uso di strutture di dati immutabili (o strutture di dati persistenti). Inizialmente ho anche avuto un po 'di zelo e ho creato un albero immutabile, una lista di collegamenti immutabili e una tabella di hash immutabile, ma nel tempo raramente ho trovato molto utile per quelli. Ho trovato principalmente il maggior uso del grosso contenitore immutabile simile a matrice nel diagramma sopra.

Ho ancora un sacco di codice che funziona con i mutabili (lo trovo una necessità pratica almeno per un codice di basso livello), ma lo stato principale dell'applicazione è una gerarchia immutabile, che passa da una scena immutabile a componenti immutabili al suo interno. Alcuni dei componenti più economici vengono ancora copiati per intero, ma quelli più costosi come mesh e immagini utilizzano la struttura immutabile per consentire quelle copie parziali a basso costo delle sole parti che devono essere trasformate.


0

Ci sono già molte buone risposte. Queste sono solo informazioni extra in qualche modo specifiche per .NET. Stavo scavando nei vecchi post del blog .NET e ho trovato un bel riassunto dei vantaggi dal punto di vista degli sviluppatori di Microsoft Immutable Collections:

  1. Semantica dell'istantanea, che ti consente di condividere le tue raccolte in modo che il destinatario possa contare su non cambiare mai.

  2. Sicurezza implicita dei thread nelle applicazioni multi-thread (non sono richiesti blocchi per accedere alle raccolte).

  3. Ogni volta che hai un membro della classe che accetta o restituisce un tipo di raccolta e desideri includere la semantica di sola lettura nel contratto.

  4. Programmazione funzionale amichevole.

  5. Consenti la modifica di una raccolta durante l'enumerazione, assicurando che la raccolta originale non cambi.

  6. Implementano le stesse interfacce IReadOnly * di cui il codice tratta già, quindi la migrazione è semplice.

Se qualcuno ti consegna un ReadOnlyCollection, un IReadOnlyList o un IEnumerable l'unica garanzia è che non puoi modificare i dati - non vi è alcuna garanzia che la persona che ti ha consegnato la raccolta non li cambierà. Tuttavia, hai spesso bisogno di una certa sicurezza che non cambierà. Questi tipi non offrono eventi per avvisarti quando cambiano i loro contenuti e, se cambiano, potrebbero verificarsi su un thread diverso, possibilmente mentre li stai enumerando? Tale comportamento porterebbe alla corruzione dei dati e / o eccezioni casuali nella tua applicazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.