2013-07-24 15 views
12

Sto dubitando della mia comprensione della semantica System.Collection.Generic.IReadOnlyCollection<T> e dubito di come progettare utilizzando concetti come sola lettura e immutabili. Permettetemi di descrivere le due nature che dubito tra utilizzando il documentation, che affermaSemantica Immutability/Read-only (particolare C# IReadOnlyCollection <T>)

Rappresenta una raccolta di elementi fortemente tipizzata, di sola lettura.

A seconda che sottolineo le parole 'rappresenta' o 'sola lettura' (quando pronunciando nella mia testa, o ad alta voce se questo è il tuo stile) Mi sento come le frasi cambiano significato:

  1. Quando sottolineo "sola lettura", la documentazione definisce a mio avviso immutabilità osservativa (un termine utilizzato in Eric Lippert's article), il che significa che un'implementazione dell'interfaccia può fare ciò che vuole purché non siano visibili mutazioni pubblicamente †.
  2. Quando sottolineo "Rappresentanti", la documentazione definisce (a mio parere, ancora una volta) una facciata immutabile (di nuovo descritta nell'articolo di Eric Lippert), che è una forma più debole, dove le mutazioni possono essere possibili, ma non possono essere fatte da l'utente. Ad esempio, una proprietà di tipo IReadOnlyCollection<T> rende chiaro all'utente (ad esempio qualcuno che codifica contro il tipo di dichiarazione) che non può modificare questa raccolta. Tuttavia, è ambiguo se il tipo di dichiarazione stesso possa modificare la raccolta.
  3. Per motivi di completezza: l'interfaccia non contiene semantica diversa da quella che porta le firme dei suoi membri. In questo caso l'osservabilità o l'immutabilità della facciata dipende dall'implementazione (non solo dall'implementazione-dell'interfaccia-dipendente, ma dall'istanza-dipendente).

La prima opzione è in realtà la mia interpretazione preferita, sebbene questo contratto possa essere facilmente risolto, ad es. costruendo un ReadOnlyCollection<T> da un array di T e quindi impostando un valore nell'array wrapper.

Il BCL ha ottime interfacce per immutabilità facciata, come ad esempio IReadOnlyCollection<T>, IReadOnlyList<T> e forse anche IEnumerable<T>, ecc, tuttavia, trovo immutabilità osservazionale anche utile e per quanto ne so, non ci sono interfacce nel carring BCL questo significato (si prega di indicarli se ho torto). Ha senso che questi non esistano, perché questa forma di immutabilità non può essere applicata da una dichiarazione di interfaccia, solo dagli implementatori (comunque un'interfaccia potrebbe portare la semantica, come mostrerò di seguito). A parte: mi piacerebbe avere questa capacità in una versione futura di C#!


Esempio: (può essere saltata) devo frequentemente per implementare un metodo che riceve come argomento un insieme che è utilizzato da un altro thread pure, ma il metodo richiede la raccolta non deve essere modificato durante la sua esecuzione e quindi dichiaro che il parametro sia di tipo IReadOnlyCollection<T> e mi concedo una pacca sulla spalla pensando di aver soddisfatto i requisiti. Sbagliato ... Per un chiamante quella firma sembra come se il metodo promettesse di non cambiare la raccolta, nient'altro, e se il chiamante prende la seconda interpretazione della documentazione (facciata) potrebbe semplicemente pensare che la mutazione sia consentita e il metodo in la domanda è resistente a questo. Anche se ci sono altre soluzioni più convenzionali per questo esempio, spero che tu possa vedere che questo problema può essere un problema pratico, in particolare quando altri stanno usando il tuo codice (o futuro - tu per quello).


Così ora al mio problema reale (che ha innescato dubitare la semantica interfacce esistenti):

Vorrei utilizzare osservazionale immutabilità e la facciata immutabilità e distinguere tra loro. Due opzioni a cui ho pensato sono:

  1. Utilizzare le interfacce BCL e documentare ogni volta se si tratta di osservabilità o solo immutabilità della facciata. Svantaggio: gli utenti che utilizzano tale codice consulteranno la documentazione solo quando è già troppo tardi, ovvero quando è stato trovato un errore. Voglio guidarli nella fossa del successo; la documentazione non può farlo). Inoltre, trovo questo tipo di semantica abbastanza importante da essere visibile nel sistema di tipi piuttosto che esclusivamente nella documentazione.
  2. Definire le interfacce che trasportano esplicitamente la semantica di immutabilità osservativa, come IImmutableCollection<T> : IReadOnlyCollection<T> { } e IImmutableList<T> : IReadOnlyList<T> { }. Si noti che le interfacce non hanno alcun membro ad eccezione di quelli ereditati. Lo scopo di queste interfacce sarebbe quello di dire solo "Persino il tipo di dichiarazione non mi cambierà!" ‡ Ho specificamente detto "non" qui in contrapposizione a "non posso". Qui si trova uno svantaggio: non è impedito a un implementatore malvagio (o errato, per rimanere educato) di interrompere questo contratto dal compilatore o qualcosa del genere. Tuttavia, il vantaggio è che un programmatore che ha scelto di implementare questa interfaccia piuttosto che quello da cui eredita direttamente, è molto probabilmente consapevole del messaggio extra inviato da questa interfaccia, poiché il programmatore è consapevole dell'esistenza di questa interfaccia ed è quindi probabile implementarlo di conseguenza.

Sto pensando di andare con la seconda opzione, ma temo che non ha problemi di progettazione paragonabili a quelli dei tipi delegato (che sono stati inventati per portare informazioni semantiche sopra le loro controparti semanticless Func e Action) e in qualche modo che non è riuscito, vedi ad es here.

Mi piacerebbe sapere se hai riscontrato/discusso di questo problema, o se sto solo litigando troppo sulla semantica e dovrei semplicemente accettare le interfacce esistenti e se sono solo inconsapevole delle soluzioni esistenti in il BCL. Sarebbe utile qualsiasi problema di progettazione come quelli sopra menzionati. Ma sono particolarmente interessato ad altre soluzioni che potresti (avere) inventato per il mio problema (che in breve significa distinguere osservabilità e immutabilità della facciata sia nella dichiarazione che nell'uso).
Grazie in anticipo.


† Sto ignorando le mutazioni dei campi ecc sugli elementi della raccolta.
‡ Questo è valido per l'esempio che ho dato in precedenza, ma l'affermazione è in realtà più ampia. Ad esempio, qualsiasi metodo di dichiarazione non lo cambierà, o un parametro di tale tipo trasmette che il metodo può aspettarsi che la collezione non cambi durante l'esecuzione (che è diverso dal dire che il metodo non può cambiare la collezione, che è l'unica affermazione che si può fare con le interfacce esistenti), e probabilmente molte altre.

+1

Forse questo è rilevante: http: //blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx –

+6

Preferisco l'uso del termine "Immutable" come parte del nome della classe per le collezioni osservabili immutabili e "Readonly" come parte del nome della classe per facciate immutabili per collezioni mutabili. Mi piacciono anche le classi non di raccolta con la parola "Immutable" nel loro nome per essere "profondamente immutabili", cioè i campi sono tutti tipi di valori readonly privati ​​o sono tipi di riferimento di tipo readonly privati ​​che sono essi stessi immutabili. ('readonly' è la parola chiave C#) (Questa sembra essere anche la convenzione adottata da Microsoft, quindi giorni felici.) –

+0

Non sono realmente a riva di quello che stai chiedendo ma IImmutablexxx significa che non cambierà (può cambiare ma la modifica genererà una nuova raccolta) IReadOnlyxxx impedisce solo al destinatario di modificare la raccolta che il provider può modificarlo. –

risposta

1

Un'interfaccia non può garantire l'immutabilità. Una parola nel nome non impedisce la mutabilità, è solo un altro suggerimento come la documentazione.

Se si desidera un oggetto immutabile, è necessario un tipo concreto immutabile. In C#, l'immutabilità dipende dall'implementazione e non è visibile in un'interfaccia.

Come ha detto Chris, è possibile trovare existing implementations of immutable collections.

+0

A parte alcune interfacce "marker", l'unica cosa che * qualsiasi * interfaccia può garantire è che qualsiasi classe che la implementa rispetterà il contratto dell'interfaccia o sarà considerata un'implementazione "rotta". Se il contratto per "ImmutableFloatMatrix" specifica che ogni chiamata a "GetValue (X, Y)" deve sempre restituire lo stesso risultato per ogni coppia data (X, Y), i consumatori non potrebbero meno supporre che un'implementazione di "ImmutableFloatMatrix" 'era immutabile che presumere che passare" Fred "' Elenco .Add' non aggiungerebbe "Barney". – supercat