2009-07-22 4 views
15

Qualcuno può spiegarmi il concetto di covarianza e controvarianza nella teoria dei linguaggi di programmazione ?Covarianza e controvarianza nei linguaggi di programmazione

+5

Sento odore di domande a casa. –

+0

[Covarianza vs contrravarianza] (http://izlooite.blogspot.com/2011/04/covariance-and-contravariance.html) –

+0

possibile duplicato di [C#: Is Variance (Covariance/Contravariance) un'altra parola per Polymorphism?] (http://stackoverflow.com/questions/1078423/c-sharp-is-variance-covariance-contravariance-another-word-for-polymorphis) – nawfal

risposta

13

covarianza è piuttosto semplice e migliore pensiero dal punto di vista di una classe di raccolta List. Possiamo parametrizzare la classe List con un parametro di tipo T. Cioè, il nostro elenco contiene elementi di tipo T per alcuni T. Lista sarebbe covariante se

S è un sottotipo di List T se e solo se [S] è un sottotipo di List [T]

(Dove sto usando la definizione matematica se e solo se a significare se e solo se.)

Cioè, un List[Apple]è unList[Fruit]. Se c'è qualche routine che accetta un parametro List[Fruit] e ho un List[Apple], allora posso passarlo come parametro valido.

def something(l: List[Fruit]) { 
    l.add(new Pear()) 
} 

Se la nostra classe di raccolta List è mutevole, allora covarianza non ha alcun senso perché potremmo supporre che la nostra routine potrebbe aggiungere qualche altro frutto (che non era una mela) come sopra. Quindi dovremmo gradire solo le classi di raccolta immutabili covariant!

+0

Buona definizione, ma manca il fatto che non solo i tipi possono essere trattati come co/controvarianti. Ad esempio, Java 'Lista ' non lo è, ma i caratteri java Java consentono di trattare in modo covariante o controverso al punto di utilizzo (piuttosto che dichiarazione) - naturalmente, limitando l'insieme di operazioni sul tipo a quelle che sono in realtà covariante e controverso per questo. –

+1

Credo che 'Elenco 'è un tipo di * tipo esistenziale *: ad esempio' Elenco [T] perAlcuni T <: Frutta' - il * forSome T <: Fruit * è di per sé un tipo in questa istanza. Tuttavia, Java non è ancora covariante in questo tipo. Ad esempio un metodo che accetta un 'Elenco 'non accetta' Elenco ' –

+0

Voglio dire" non accetterebbe un 'Elenco ' ovviamente –

14
+1

Questa è una spiegazione talmente buona, non di tipo Book. –

+1

Heck - Mi è anche piaciuto leggerlo. Più di Wikipedia :) – xtofl

+0

Bene FWIW, la spiegazione è da una discussione che io ei miei colleghi abbiamo avuto con un membro eminente (ex HP) quando la discussione ha virato verso OOSC di Bertrand Meyers in cui penso che Meyers sottolinei l'importanza della contravarianza in OOP. di B.Meyers è un libro assolutamente da leggere se si vuole capire praticamente l'OOP – Abhay

6

Qui ci sono i miei articoli su come abbiamo aggiunto nuove caratteristiche varianza di C# 4.0. Inizia dal basso.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

+0

OT: Eric, hai postato un enigma su una delle domande precedenti. Non ho potuto risolverlo e ho fatto domande al riguardo. Potresti per favore guardarlo? http://stackoverflow.com/questions/1167666/c-puzzle-reachable-goto-pointing-to-an-unreachable-label – SolutionYogi

+0

OMG Eric Lippert !!!! – Janie

+1

Guardalo con quei punti esclamativi lì. Puoi mettere gli occhi di qualcuno con una di quelle cose. –

0

Bart De Smet ha una grande voce di blog su covarianza & controvarianza here.

4
4

C'è una distinzione tra covarianza e controvarianza.
Molto approssimativamente, un'operazione è covariante se conserva l'ordinamento dei tipi e controverso se è inverte questo ordine.

L'ordine stesso è pensato per rappresentare tipi più generali più grandi di tipi più specifici.
Ecco un esempio di una situazione in cui C# supporta la covarianza. In primo luogo, questo è un array di oggetti:

object[] objects=new object[3]; 
objects[0]=new object(); 
objects[1]="Just a string"; 
objects[2]=10; 

Naturalmente è possibile inserire diversi valori nell'array perché alla fine tutti derivano da System.Object nel framework. In altre parole, System.Object è un tipo molto generale o grande. Ora qui è un punto in cui è supportato covarianza:
assegnando un valore di un tipo più piccolo a una variabile di un tipo più grande

string[] strings=new string[] { "one", "two", "three" }; 
objects=strings; 

Gli oggetti variabili, che è di tipo object[], in grado di memorizzare un valore è infatti di tipo string[].

Pensateci: ad un certo punto, è quello che vi aspettate, ma poi di nuovo non lo è. Dopotutto, mentre string deriva da object, string[]NON corrisponde a da object[]. Il supporto linguistico per la covarianza in questo esempio rende comunque possibile l'assegnazione, che è una cosa che troverai in molti casi. La varianza è una funzionalità che rende il linguaggio più intuitivo.

Le considerazioni su questi argomenti sono estremamente complicate. Ad esempio, in base al codice precedente, qui ci sono due scenari che causeranno errori.

// Runtime exception here - the array is still of type string[], 
// ints can't be inserted 
objects[2]=10; 

// Compiler error here - covariance support in this scenario only 
// covers reference types, and int is a value type 
int[] ints=new int[] { 1, 2, 3 }; 
objects=ints; 

Un esempio per il funzionamento della controvarianza è un po 'più complicato.Immaginate questi due classi:

public partial class Person: IPerson { 
    public Person() { 
    } 
} 

public partial class Woman: Person { 
    public Woman() { 
    } 
} 

Woman è derivato da Person, ovviamente. Consideriamo ora si dispone di queste due funzioni:

static void WorkWithPerson(Person person) { 
} 

static void WorkWithWoman(Woman woman) { 
} 

Una delle funzioni fa qualcosa (non importa cosa) con un Woman, l'altro è più generale e può funzionare con qualsiasi tipo derivato da Person. Sul lato Woman delle cose, ora avete anche questi:

delegate void AcceptWomanDelegate(Woman person); 

static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) { 
    acceptWoman(woman); 
} 

DoWork è una funzione che può prendere un Woman e un riferimento a una funzione che prende anche un Woman, e poi passa l'istanza di Woman a il delegato. Considera il polimorfismo degli elementi che hai qui. Person è più grande di Woman e WorkWithPerson è più grande di WorkWithWoman. WorkWithPerson è considerato più grande rispetto allo AcceptWomanDelegate ai fini della varianza.

Infine, ci sono queste tre righe di codice: viene creato

Woman woman=new Woman(); 
DoWork(woman, WorkWithWoman); 
DoWork(woman, WorkWithPerson); 

Un Woman esempio. Quindi viene chiamato DoWork passando l'istanza Woman e un riferimento al metodo WorkWithWoman. Quest'ultimo è ovviamente compatibile con il tipo di delegato AcceptWomanDelegate - un parametro di tipo Woman, nessun tipo di reso. La terza riga è un po 'strana, però. Il metodo WorkWithPerson accetta un parametro Person come parametro, non un Woman, come richiesto da AcceptWomanDelegate. Tuttavia, WorkWithPerson è compatibile con il tipo di delegato. controvarianza rende possibile, quindi nel caso di delegati il ​​tipo più grande WorkWithPerson può essere memorizzato in una variabile del tipo più piccolo AcceptWomanDelegate. Ancora una volta è la cosa intuitiva: se WorkWithPerson può funzionare con qualsiasi Person, passando in un Woman non può essere sbagliato, giusto?

A questo punto, ci si potrebbe chiedere come tutto ciò si riferisca ai generici. La risposta è che la varianza può essere applicata anche ai generici. L'esempio precedente utilizzava gli array object e string. Ecco il codice utilizza elenchi generici al posto degli array:

List<object> objectList=new List<object>(); 
List<string> stringList=new List<string>(); 
objectList=stringList; 

Se si prova questo fuori, troverete che questo non è uno scenario supportato in C#. In C# versione 4.0 e .NET Framework 4.0, il supporto varianza generici è stato ripulito, ed è ora possibile utilizzare le nuove parole chiave in e fuori con parametri di tipo generico. Possono definire e limitare la direzione del flusso di dati per un particolare parametro di tipo, consentendo il funzionamento della varianza.Ma nel caso di List<T>, i dati del tipo T fluiscono in entrambe le direzioni: esistono metodi sul tipo List<T> che restituiscono valori T e altri che ricevono tali valori.

Il punto di queste restrizioni direzionali è consentire varianza cui ha senso, ma per prevenire problemi come l'errore di runtime menzionato in uno dei precedenti esempi di matrice. Quando i parametri di tipo decorate correttamente con in o fuori, il compilatore può controllare, e consentire o non consentire, a sua varianza fase di compilazione. Microsoft è andato allo sforzo di aggiungere queste parole chiave per molte interfacce standard nel framework .Net, come IEnumerable<T>:

public interface IEnumerable<out T>: IEnumerable { 
    // ... 
} 

per questa interfaccia, il flusso di dati di tipo T oggetti è chiara: possono essere solo recuperato dai metodi supportati da questa interfaccia, non passati a loro. Come risultato, è possibile costruire un esempio simile al List<T> tentativo descritto in precedenza, ma utilizzando IEnumerable<T>:

IEnumerable<object> objectSequence=new List<object>(); 
IEnumerable<string> stringSequence=new List<string>(); 
objectSequence=stringSequence; 

Questo codice è accettabile per il compilatore C# dalla versione 4.0 perché IEnumerable<T> è covariante dovuto al fuori Identificatore sul parametro tipo T.

Quando si lavora con tipi generici, è importante essere consapevoli della varianza e del modo in cui il compilatore sta applicando vari tipi di trucchi per far sì che il codice funzioni come previsto.

C'è altro da sapere sulla varianza di quanto descritto in questo capitolo, ma questo è sufficiente per rendere comprensibile tutto il codice.

Rif:

0

Sia C# e il CLR consentono di covarianza e contra-varianza di tipi di riferimento quando vincolanti un metodo per un delegato. Covarianza significa che un metodo può restituire un tipo che è derivato dal tipo di reso del delegato. Contro-varianza significa che un metodo può assumere un parametro che è una base del tipo di parametro del delegato. Ad esempio, dato un delegato definito in questo modo:

delegate Oggetto MyCallback (FileStream s);

è possibile costruire un'istanza di questo tipo delegate legato ad un metodo che viene prototipato

simili:

String SomeMethod (Stream s);

Qui, il tipo di ritorno (String) di SomeMethod è un tipo derivato dal tipo di reso del delegato (Object); questa covarianza è permessa.Il tipo di parametro SomeMethod (Stream) è un tipo che è una classe base del tipo di parametro del delegato (FileStream); questa contro-varianza è consentita.

Si noti che covarianza e contro-varianza sono supportate solo per i tipi di riferimento, non per i tipi di valore o per void. Quindi, ad esempio, non posso associare il seguente metodo al delegato MyCallback:

Int32 SomeOtherMethod (Stream s);

Anche se di tipo SomeOtherMethod ritorno (Int32) è derivato da MyCallback di tipo restituito (Object), questa forma di covarianza non è consentito perché Int32 è un tipo di valore.

Ovviamente, la ragione per cui i tipi di valore e vuoto non possono essere utilizzati per covarianza e contra-varianza è perché la struttura di memoria per queste cose varia, mentre la struttura memoria per i tipi di riferimento è sempre un puntatore. Fortunatamente, il compilatore C# produce un errore se si tenta di fare qualcosa che non è supportato.