Domanda

Qualcuno mi, il concetto di covarianza e controvarianza in può spiegare la programmazione teoria lingue?

È stato utile?

Soluzione

covarianza è piuttosto semplice e migliore pensiero dal punto di vista di una classe di raccolta List. Siamo in grado di Parametrizzazione la classe List con qualche parametro di tipo T. Vale a dire, la nostra lista contiene elementi di tipo T per qualche 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 per dire se e solo se ).

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

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

Se il nostro List classe di raccolta è mutevole, allora covarianza non ha senso perché potremmo supporre che la nostra routine potrebbe aggiungere qualche altro frutto (che non era una mela) come sopra. Quindi dovremmo solo come immutabile classi di raccolta per essere covariante!

Altri suggerimenti

Ecco i miei articoli su come abbiamo aggiunto nuove funzionalità varianza per C # 4.0. Partire dal basso.

http://blogs.msdn.com/ ericlippert / archivio / tag / covarianza + e + controvarianza / default.aspx

C'è una distinzione tra di covarianza e controvarianza .
Molto grosso modo, l'operazione è covariante se conserva l'ordinamento dei tipi, e controvariante se rovesci questo ordine.

L'ordinamento si vuole rappresentare tipi più generali come maggiore di tipi più specifici.
Ecco un esempio di una situazione in cui C # supporta 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, è un System.Object molto generale o grande tipo. Ora qui è un punto in cui è supportato covarianza:
l'assegnazione di 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 che è in realtà di tipo string[].

Pensateci - a un certo punto, è quello che ci si aspetta, ma poi di nuovo non lo è. Dopo tutto, mentre string deriva da object, string[] non deriva da object[]. Il supporto per la lingua per la covarianza in questo esempio rende possibile l'assegnazione in ogni caso, che è qualcosa che troverete in molti casi. varianza è una caratteristica che rende il lavoro di lingua in modo più intuitivo.

Le considerazioni intorno a queste tematiche sono estremamente complicati. Ad esempio, in base al codice precedente, qui sono due scenari che si tradurrà in 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 di 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ò assumere un Woman e un riferimento a una funzione che prende anche un Woman, e quindi passa l'istanza di Woman al delegato. Si consideri il polimorfismo degli elementi che avete qui. Person è più di Woman, e WorkWithPerson è più di WorkWithWoman. WorkWithPerson è anche considerato maggiore che AcceptWomanDelegate ai fini della varianza.

Infine, ci sono queste tre righe di codice:

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

Viene creata un'istanza Woman. Poi DoWork viene chiamato, passando l'istanza Woman nonché un riferimento al metodo WorkWithWoman. Quest'ultimo è ovviamente compatibile con il tipo delegato AcceptWomanDelegate - un parametro di tipo Woman, nessun tipo di ritorno. La terza linea è un po 'strano, però. Il metodo WorkWithPerson prende un Person come parametro, non un Woman, come richiesto dalla AcceptWomanDelegate. Tuttavia, WorkWithPerson è compatibile con il tipo delegato. controvarianza consente, quindi nel caso di delegati del tipo più grande WorkWithPerson può essere memorizzato in una variabile di tipo AcceptWomanDelegate più piccolo. Ancora una volta è la cosa intuitiva:? se WorkWithPerson può funzionare con qualsiasi Person, passando in un Woman non può essere sbagliato , a destra

A questo punto, ci si potrebbe chiedere come tutto questo si riferisce a farmaci generici. La risposta è che la varianza può essere applicata ai generici pure. Ilprecedente esempio utilizzato object e string array. 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 il con i parametri di tipo generico. Essi possono definire e limitare la direzione del flusso di dati per un particolare parametro di tipo, consentendo di lavorare varianza. Ma nel caso di List<T>, i dati di tipo T flussi in entrambe le direzioni -. Ci sono metodi sul tipo List<T> che restituiscono valori T, e altri che ricevono tali valori

Il punto di queste restrizioni direzionali è per consentire varianza dove ha senso , ma a problemi prevenire come l'errore di runtime menzionato in uno dei precedenti esempi di matrice. Quando i parametri di tipo decorate correttamente con in o su , il compilatore può controllare, e consentire o non consentire, la sua varianza a 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 del tipo oggetti T è chiaro: si può sempre e solo essere recuperati da metodi supportati da questa interfaccia, non passò nelle loro . Come risultato, è possibile costruire un esempio simile al tentativo List<T> 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 # a partire dalla versione 4.0, perché IEnumerable<T> è covariante dovuto al il identificatore del parametro di tipo T.

Quando si lavora con i tipi generici, è importante essere consapevoli della varianza e il modo in cui il compilatore sta applicando vari tipi di inganno al fine di rendere il vostro lavoro di codice il modo in cui ci si aspetta che.

C'è di più da sapere su varianza rispetto è coperto in questo capitolo, ma questo è sufficiente per rendere ogni ulteriore codice comprensibile.

Rif:

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

Sia C # e il CLR permettono di covarianza e contra-varianza di tipi di riferimento quando associazione di un metodo per un delegato. Covarianza significa che un metodo può restituire un tipo che è derivato dal tipo di ritorno del delegato. Contra-varianza significa che un metodo può prendere parametro che è una base di tipo di parametro del delegato. Ad esempio, in un delegato definita in questo modo:

MyCallback delegato Object (FileStream s);

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

in questo modo:

String SomeMethod (Stream s);

Qui, di SomeMethod tipo di ritorno (String) è un tipo che è derivato dal tipo di ritorno del delegato (Object); questo covarianza è permesso. di SomeMethod parametro di tipo (Stream) è un tipo che è una classe base del tipo di parametro del delegato (FileStream); questo contra-varianza è consentito.

Si noti che covarianza e contrangoli varianza sono supportate solo per i tipi di riferimento, non per tipi di valori o per vuoto. Così, per esempio, non riesco a legare il seguente metodo al delegato MyCallback:

Int32 SomeOtherMethod (Stream s);

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

Ovviamente, il motivo 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 di memoria per i tipi di riferimento è sempre un puntatore. Fortunatamente, il C # compilatore produrre un errore se si tenta di fare qualcosa che non è supportato.

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