Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Il Problema della Non-Covarianza nei Generics in Java e C#

Posted by Ricibald on April 11th, 2008

Java, C# e C++ non contemplano il concetto di covarianza:

Una classe C<T> si dice covariante rispetto a T se, date due classi A e B tali che B deriva da A, risulta anche che C<B> deriva da C<A>.

Significa quindi che questa istruzione solleva un’eccezione:

List<Persona> studenti;
List<Object> objects = (List<Object>) studenti; // Errore

Questo deriva da un motivo di logica Object Oriented, non strutturale del linguaggio. Infatti il cast verso “objects” altera il comportamento di “studenti”, che accetta come elementi della lista solo oggetti che derivano da “Studente”. Se fosse stato ammesso, ciclare la collezione “studenti” avrebbe potuto restituire oggetti anche non di tipo “Studente” che non è ammesso. In altre parole, il problema è che la covarianza consente di definire sottoclassi che restringono la classe base.

Il concetto di covarianza si ripercuote sull’utilizzo dei metodi. Ad esempio: immaginiamo il metodo di una nostra implementazione della classe List:

class List<T> {
    AddAll(List<T> l);
}

Immaginiamo di avere una classe Studente che deriva da Persona:

List<Studente> studenti = new List<Studente>();
List<Persona> persone = new List<Persona>();
studenti.AddAll(studenti); // Ok
studenti.AddAll(persone);  // Errore

Il codice funziona se introduciamo wildcard (Java) o naked constraint (C#):

// Java
class List<T> {
    addAll(List<? extends T> l);
}
// C#
class List<T> {
    AddAll<U>(List<U> l) where U : T;
}

Se invece vogliamo staccarci dalla genericità e trattare solo istanze di tipo “Object” osserviamo (come abbiamo già visto) che questo non compila:

List<Object> l = (List<Object>)persone; // Errore

Ma utilizzando wildcard (Java) o collezioni raw (C#) la classe compila:

List<?> l = persone; // Java
IList l = persone;   // C#

Da cui deriva che in C#, quando realizziamo una classe generica, è necessario fornire sempre un’implementazione non generica per supportare upcast non generici:

// C#
class List {
    Add(object item);
    AddAll(List l);
}
class List<T> : List {
    new Add(T item);
    new AddAll<U>(List<U> l) where U : T;
}

Si noti infine il differente approccio Java/C#:

  • Le librerie Java forniscono un supporto al problema dell’upcast:
    • I metodi sono già predisposti (si veda addAll, che accetta in input una Collection<? extends E>)
    • Il cast verso tipi raw è di default supportato (List<?>)
  • Le librerie C# non sono pensate per il supporto verso l’upcast:
    • I metodi non prevedono naked constraint (AddAll accetta in input solo una List<T>). I naked constraint sembrano più che altro un workaround non dichiarato
    • Il cast verso tipi raw è fornito solo se viene fornita un’implementazione non generica della classe (è quindi richiesto l’intervento implementativo dello sviluppatore)

Leave a Reply

You must be logged in to post a comment.