2010-01-03 13 views
15

Stavo leggendo un'intervista con Joshua Bloch in Coders at Work, dove si lamentava dell'introduzione di generici in Java 5. Non gli piace l'implementazione specifica, in gran parte perché il supporto della varianza, i caratteri jolly di Java, lo rende inutilmente complesso.Generici C#: senza limiti inferiori per progettazione?

Per quanto ne so, C# 3 non ha nulla come i caratteri jolly espliciti, limitati, ad es. non è possibile dichiarare un metodo PriceBatch che richiede una raccolta di asset o qualsiasi sottoclasse di asset (void PriceBatch(Collection<? extends Asset> assets) in Java?).

Qualcuno sa perché i caratteri jolly e i limiti non sono stati aggiunti a C#? Queste funzioni sono state intenzionalmente tralasciate per rendere più semplice la lingua, oppure è qualcosa che non sono ancora state in grado di implementare?

MODIFICA: Santo fumo, commenti da Eric Lippert stesso! Dopo aver letto le sue e le osservazioni penetranti di Paolo, mi rendo conto che almeno sono supportati limiti superiori e che l'esempio di cui sopra può essere tradotto in C# come:

void PriceBatch<T>(ICollection<T> assets) where T : Asset 

limiti più bassi, d'altra parte, sono a quanto pare non supportato come Eric dice nel suo secondo commento, ad es probabilmente non c'è modo di tradurre direttamente questo (un po 'forzato) codice Java in C#:

public class Asset {} 
public class Derivative extends Asset {} 
public class VanillaOption extends Derivative {} 

public static <T extends Asset> void copyAssets(Collection<T> src, Collection<? super T> dst) { 
    for(T asset : src) dst.add(asset); 
} 

Collection<VanillaOption> src = new ArrayList<VanillaOption>(); 
[...] 
Collection<Derivative> dst = new ArrayList<Derivative>(); 
[...] 
copyAssets(src, dst); 

Sono corretto? Se questo è il caso, c'è un motivo particolare per cui C# ha limiti superiori ma non inferiori?

+5

Non capisco la domanda; C# ha limitato i generici e da allora C# 2. È quello che stai ottenendo che Java supporta * call-site * covarianza e controvarianza sui tipi generici costruiti? Perché C# non supporta questo; C# 4 supporterà la * dichiarazione-sito * varianza. –

+6

O è ciò che si ottiene dal fatto che i limiti dei parametri di tipo sui metodi generici devono solo derivare dai limiti e non devono essere derivati ​​dai limiti? C# non supporta quest'ultimo; Java e Scala credo. –

+0

Grazie, ho aggiornato la domanda. – ehnmark

risposta

20

Una domanda complicata.

Prima consideriamo la tua domanda fondamentale, "perché questo è illegale in C#?"

class C<T> where T : Mammal {} // legal 
class D<T> where Giraffe : T {} // illegal 

Cioè, un tipo di vincolo generico può dire 'T deve essere qualsiasi tipo di riferimento che può essere assegnato ad una variabile di tipo Mammifero', ma non" T deve essere qualsiasi tipo di riferimento, una variabile di cui potrebbe essere assegnata una giraffa ".Perché la differenza?

Non so. Questo è stato molto tempo prima che il mio tempo sul team C#. La risposta banale è" perché il CLR non lo supporta ", ma la squadra quello progettato C# generics era lo stesso team che ha progettato generici CLR, quindi non è molto di una spiegazione

La mia ipotesi sarebbe semplicemente che come sempre, per essere supportata, una funzionalità deve essere progettata, implementata, testata, documentata e spedita ai clienti; nessuno ha mai fatto nessuna di queste cose per questa funzione, e quindi non è nella lingua. Non vedo un grande vantaggio per la funzionalità proposta; le caratteristiche complicate senza vantaggi convincenti tendono ad essere tagliate qui.

Tuttavia, questa è una supposizione. La prossima volta mi capita di chiacchierare con i ragazzi che hanno lavorato con i generici - vivono in Inghilterra, quindi non è come se fossero in fondo a me, purtroppo - chiederò.

Per quanto riguarda il tuo esempio specifico, penso che Paul sia corretto. Non hai bisogno di vincoli di limite inferiore per farlo funzionare in C#. Si potrebbe dire:

void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U 
{ 
    foreach(T item in src) dst.Add(item); 
} 

Cioè, mettere il vincolo su T, non su U.

+1

Grazie per il follow-up.Il libro Java efficace spiega uno scenario in cui ciò sarebbe utile: ad esempio, definisci un tipo "MyStack " con un metodo 'PopAll' che accetta come destinazione una raccolta di destinazione. Se possiedi un 'MyStack ' dovresti essere in grado di copiare i suoi elementi in un 'Elenco ', ma per esprimere che dovresti dire qualcosa come public void 'PopAll (ICollection dst) dove T: X' e questo sembra essere davvero illegale. – ehnmark

+7

Sì, sarebbe utile. È interessante notare che in C# anche se non è possibile farlo direttamente, è possibile * farlo facendo un metodo di estensione su Stack ! Ciò introduce un nuovo parametro di tipo che può quindi essere limitato. –

+0

Un altro esempio ho potuto vedere sarebbe covarianza di 'IImmutableList ' in 'System.Collections.Immutable'; Attualmente, 'IImmutableList Add (T articolo)' impedisce che, ma un 'IImmutableList Add (tNuovo articolo) dove tNuovo: T' sarebbe salvarlo. (Beh, per prima cosa non implementeremmo l'interfaccia mutabile sul tipo immutabile, ma suppongo che avessero motivi per andare con quel progetto.) Qui, i metodi di estensione non ti avrebbero portato lontano. – fuglede

8

C# 4 introduce nuove funzionalità che consentono la covarianza e la contravarianza nei generici.

Ci sono altri SO post che parlare di questo in modo più dettagliato: How is Generic Covariance & Contra-variance Implemented in C# 4.0?

La nuova funzione non attiva automaticamente in tutti i tipi, ma c'è una nuova sintassi che permette agli sviluppatori di specificare se gli argomenti generici sono covariante o controverso.

Le versioni C# precedenti a C# 4 avevano funzionalità limitate simili a questa in quanto riguardano delegati e determinati tipi di array. Per quanto riguarda i delegati, i delegati che accettano tipi di parametri di base sono consentiti. Per quanto riguarda i tipi di array, penso che sia valido a meno che non ci sia il pugilato coinvolto. Cioè, una matrice di clienti può essere il caso di una matrice di oggetti. Tuttavia, non è possibile eseguire il cast di una matrice di int in una matrice di oggetti.

+0

Array: String [] è anche un oggetto []. – codekaizen

+0

Aggiornato la mia risposta, grazie. – Eilon

+2

Le raccolte/gli array mutabili devono essere non varianti. Altrimenti rompe la sicurezza del tipo. –

7

NET ha già l'equivalente di caratteri jolly, più logicamente chiamati vincoli di tipo generico, si può fare ciò che si descrive senza problemi

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
     List<a> a = new List<a>(); 
     List<b> b = new List<b>(); 
     List<c> c = new List<c>(); 
     test(a); 
     test(b); 
     test(c); 

     } 

     static void test<T>(List<T> a) where T : a 
     { 
      return; 
     } 
    } 
    class a 
    { 

    } 
    class b : a 
    { 

    } 
    class c : b 
    { 

    } 
} 

Esempio 2

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      ICollection<VanillaOption> src = new List<VanillaOption>(); 
     ICollection<Derivative> dst = new List<Derivative>(); 
     copyAssets(src, dst); 
     } 

     public static void copyAssets<T,G>(ICollection<T> src, ICollection<G> dst) where T : G { 
      foreach(T asset in src) 
       dst.Add(asset); 
     } 
    } 
    public class Asset {} 
    public class Derivative : Asset {} 
    public class VanillaOption : Derivative {} 
} 

Questo esempio rappresenta una conversione del codice dal tuo esempio in java.

Non sono davvero in grado di rispondere alla domanda vera!

+0

Ciao Paul, una domanda: l'implicazione del tuo esempio di codice è che il vincolo generico espresso in "dove T: a" ... in virtù del fatto che "gestirà" non solo un'istanza di "a" stessa, ma , inoltre, qualsiasi oggetto disceso, non importa quanto "indirettamente", da "a": è equivalente a caratteri jolly in generici in Java? Sto solo cercando di chiarire a mio vantaggio; non ci sono critiche sulla tua risposta implicita o intenzionale. Il fatto che la lettera "a" abbia tre significati diversi (classe, variabile interna e nome parametro) nel tuo esempio mi ha reso più difficile da digerire, fyi. – BillW

+0

Sì, è un po 'poco chiaro, scuse. Ho scritto un esempio del tuo codice limitato, funzionalmente è lo stesso, ma non usa limiti inferiori! –

+0

Ciao Paul e grazie per la tua risposta aggiornata. La mia domanda è davvero perché C# 3 ha solo un sottoinsieme del supporto varianza di Java in questo senso e se è una decisione intenzionale di fare "bene" ciò che Java ha "sbagliato", o ha a che fare con limitazioni CLR o quant'altro. È difficile trovare un esempio significativo di quando i limiti inferiori sarebbero utili, come illustra il mio esempio inventato, e forse questa è parte della risposta :) – ehnmark