In che modo la varianza del sito di utilizzo di Java si confronta con la varianza del sito di dichiarazione di C#?

StackOverflow https://stackoverflow.com/questions/4231305

  •  26-09-2019
  •  | 
  •  

Domanda

La mia comprensione è che la specifica della varianza per i generici in C# avviene a livello di dichiarazione del tipo:quando crei il tuo tipo generico, specifichi la varianza per gli argomenti del tipo.In Java, invece, la varianza viene specificata laddove viene utilizzato un generico:quando crei una variabile di tipo generico, specifichi come possono variare i suoi argomenti di tipo.

Quali sono i pro e i contro di ciascuna opzione?

È stato utile?

Soluzione

Sto solo andando a rispondere alle differenze tra la dichiarazione in loco e la varianza utilizzo in loco, dal momento che, mentre il C # e Java generici differiscono in molti altri modi, queste differenze sono per lo più ortogonale alla varianza.

Prima di tutto, se non ricordo male usare loco varianza è strettamente più potente di varianza dichiarazione loco (anche se a costo di concisione), o per lo meno i caratteri jolly di Java sono (che sono in realtà più potenti di varianza utilizzo in loco) . Questo aumento di potenza è particolarmente utile per le lingue nelle quali costrutti stateful vengono utilizzati pesantemente, come C # e Java (ma Scala molto meno, soprattutto perché la sua lista standard sono immutabili). Considerare List<E> (o IList<E>). Dal momento che ha metodi sia per l'aggiunta di E e ottenere E di, è invariante rispetto alla E, e così la dichiarazione loco varianza non può essere utilizzato. Tuttavia, con la varianza utilizzo in loco si può solo dire List<+Number> per ottenere il sottoinsieme covariante di List e List<-Number> per ottenere il sottoinsieme controvariante di List. In un linguaggio dichiarazione posto il progettista della biblioteca avrebbe dovuto fare interfacce separate (o classi se si consente l'ereditarietà multipla di classi) per ogni sottoinsieme e hanno List estendere tali interfacce. Se il progettista libreria non lo fa (nota che IEnumerable C # 's fa solo un piccolo sottoinsieme della porzione covariante di IList), allora sei fuori di fortuna e si deve ricorrere agli stessi fastidi che si hanno a che fare in una lingua senza qualsiasi tipo di varianza.

In modo che sia i vantaggi di utilizzo in loco eredità over dichiarazione loco eredità. Il vantaggio di dichiarazione loco successione sull'uso loco ereditarietà è fondamentalmente concision per l'utente (a condizione che il progettista ha attraversato lo sforzo di separare ogni classe / interfaccia nella sua covariante e porzioni controvarianti). Per qualcosa come IEnumerable o Iterator, è bello non dover specificare covarianza ogni volta che si utilizza l'interfaccia. Java ha fatto questo particolarmente fastidioso utilizzando una sintassi lunga (ad eccezione di bivariance per i quali la soluzione di Java è fondamentalmente l'ideale).

Naturalmente, queste due caratteristiche del linguaggio possono coesistere. Per parametri di tipo che sono naturalmente covariante o contravariant (come nel IEnumerable / Iterator), dichiarare così nella dichiarazione. Per i parametri di tipo che sono naturalmente invarianti (come ad esempio in (I)List), dichiara che tipo di varianza si desidera che ogni volta che si utilizza. Basta non specificare una varianza utilizzo in loco per gli argomenti con una varianza dichiarazione loco, che fa solo le cose confusione.

Ci sono altre questioni più dettagliate che non sono andati in (ad esempio come i caratteri jolly sono in realtà più potente di varianza utilizzo in loco), ma spero che questo risponde alla tua domanda ai tuoi contenuti. Devo ammettere che sono di parte verso varianza utilizzo in loco, ma ho cercato di ritrarre i principali vantaggi di entrambi che sono venuti nelle mie discussioni con i programmatori e con i ricercatori della lingua.

Altri suggerimenti

La maggior parte delle persone sembra preferire la varianza del sito di dichiarazione, perché rende più semplice il utenti della libreria (rendendo il tutto un po' più difficile per lo sviluppatore della libreria, anche se direi che lo sviluppatore della libreria deve pensare alla varianza indipendentemente da dove la varianza è effettivamente scritta).

Ma tieni presente che né Java né C# sono esempi di buona progettazione del linguaggio.

Mentre Giava ha ottenuto la varianza giusta e funziona indipendentemente dalla JVM a causa dei miglioramenti della VM compatibili in Java 5 e della cancellazione del tipo, la varianza del sito di utilizzo rende l'utilizzo un po' macchinoso e la particolare implementazione della cancellazione del tipo ha attirato meritate critiche.

C#Il modello di varianza del sito di dichiarazione di toglie l'onere all'utente della libreria, ma durante l'introduzione dei generici reificati hanno sostanzialmente incorporato le regole di varianza nella loro VM.Ancora oggi non possono supportare pienamente la co-/controvarianza a causa di questo errore (e l'introduzione non compatibile con le versioni precedenti delle classi di raccolta reificate ha diviso i programmatori in due campi).

Ciò pone una difficile restrizione su tutti i linguaggi destinati a CLR ed è uno dei motivi per cui i linguaggi di programmazione alternativi sono molto più vivaci sulla JVM, anche se sembra che CLR abbia "caratteristiche molto più interessanti".

Guardiamo Scala:Scala è un ibrido funzionale completamente orientato agli oggetti in esecuzione sulla JVM.Usano la cancellazione del tipo come Java, ma sia l'implementazione di Generics che la varianza (del sito di dichiarazione) sono più facili da comprendere, più semplici e potenti di quelle di Java (o C#), perché la VM non impone regole su come deve essere modificata la varianza. lavoro.Il compilatore Scala controlla le notazioni della varianza e può rifiutare il codice sorgente non corretto in fase di compilazione invece di generare eccezioni in fase di runtime, mentre i file .class risultanti possono essere utilizzati senza problemi da Java.

Uno svantaggio della varianza del sito di dichiarazione è che in alcuni casi sembra rendere più difficile l'inferenza del tipo.

Allo stesso tempo Scala può utilizzare tipi primitivi senza inserirli in raccolte come in C# utilizzando il metodo @specialized annotazione che dice al compilatore Scala di generare una o più implementazioni aggiuntive di una classe o metodo specializzato nel tipo primitivo richiesto.

Scala può anche "quasi" reificare i generici utilizzando Manifests che consente loro di recuperare i tipi generici in fase di runtime come in C#.

Svantaggi di farmaci generici in stile Java

Una delle conseguenze è che la versione di Java funziona solo con i tipi di riferimento (o valore-tipo in scatola) e non di valore-tipi. IMO questo è il più grande svantaggio in quanto impedisce generici ad alte prestazioni in un sacco di scenari e richiede la scrittura manuale dei tipi specializzati.

E 'non garantisce un invariante del tipo "Questa lista contiene solo oggetti di tipo x" e le esigenze di runtime controlla ad ogni getter. Il tipo generico esiste davvero.

Quando si utilizza la riflessione non si può chiedere a un caso di un oggetto generico quali parametri generici che ha.

I vantaggi di farmaci generici in stile Java

Si ottiene varianza / può lanciare tra i diversi parametri generici.

Java: i generici Usa-site varianza dal array covarianti Java 5. rotte con una sintassi diversa dal 1.0. Nessun tipo di runtime informazioni di farmaci generici.

C #: farmaci generici Usa-site varianza dal C # 2.0. Inserito il sito varianza dichiarazione in C # 4.0. Rotto array covariante con una sintassi differente dal 1.0 (edizione identica a Java). "Reificati" farmaci generici che significa che informazioni di tipo non si perde in fase di compilazione.

Scala: Sia l'uso-site / dichiarazione-site varianza poiché le prime versioni del linguaggio (almeno dal 2008). Gli array non sono una caratteristica del linguaggio a parte, in modo da utilizzare gli stessi farmaci generici sintassi e regole di tipo varianza. Certe collezioni sono implementati a livello di VM con gli array JVM in modo da ottenere prestazioni uguali o migliori di esecuzione rispetto al codice Java.

Per elaborare il / Java problema C # sicurezza tipo di matrice: è possibile lanciare un cane [] a un animale domestico [] e aggiungere un gatto e innescare un errore di runtime che non viene catturato in fase di compilazione. Scala implementato correttamente questo.

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