2010-03-23 14 views
5

Il caso d'uso è qualche cosa di simile: Perché il concetto di "Covarianza" e "Contravarianza" sono applicabili mentre si implementano i metodi di un'interfaccia?

public class SomeClass: IClonable 
{ 
    // Some Code 

    //Implementing interface method 
    Public object Clone() 
     { 
     //Some Clonning Code 
     } 
} 

Ora metodo

la mia domanda è: "Perché non è possibile usare 'SomeClass (Come è derivd da objec)' come un tipo di ritorno di Clone() se consideriamo le Funda di della covarianza e controvarianza.

qualcuno può spiegare il motivo alla base di questo implemementaion di Microsoft ????

+1

Se l'interfaccia 'IClonable' è stata scritta prima del codice, in che modo il metodo Clone può restituire tipi specifici? L'unica garanzia che abbiamo è che qualsiasi cosa venga restituita dalla tua implementazione di Clone, sarà derivata da "Oggetto". Dato che devi aderire alla definizione esatta dell'interfaccia, dovrai restituire ciò che l'interfaccia dice, essendo "Oggetto". –

+0

Qui voglio che la mia implementazione del metodo Clone dovrebbe essere in grado di restituire un tipo specifico non il metodo Clone() di IClonable quando sto installando l'interfaccia IClonable. Ma questo non è permesso, otteniamo un errore di compilazione. Quindi voglio sapere il motivo, perché il concetto di Covariance o Contravariance non è consentito per l'implementazione di un'interfaccia. – Amit

risposta

4

Un'implementazione non interrotta della varianza di implementazione dell'interfaccia dovrebbe essere covariante nel tipo di ritorno e controvariante nei tipi di argomento.

Ad esempio:

public interface IFoo 
{ 
    object Flurp(Array array); 
} 

public class GoodFoo : IFoo 
{ 
    public int Flurp(Array array) { ... } 
} 

public class NiceFoo : IFoo 
{ 
    public object Flurp(IEnumerable enumerable) { ... } 
} 

Entrambi sono legali sotto le "nuove" regole, giusto? Ma che dire di questo:

public class QuestionableFoo : IFoo 
{ 
    public double Flurp(Array array) { ... } 
    public object Flurp(IEnumerable enumerable) { ... } 
} 

Tipo di difficile dire quale implementazione implicita è migliore qui. Il primo è una corrispondenza esatta per il tipo di argomento, ma non il tipo di ritorno. Il secondo è una corrispondenza esatta per il tipo di ritorno, ma non il tipo di argomento. Mi sto orientando verso il primo, perché chiunque utilizzi l'interfaccia IFoo può dargli sempre un Array, ma non è ancora del tutto chiaro.

E questo non è il peggiore, di gran lunga. Cosa succede se invece lo facciamo:

public class EvilFoo : IFoo 
{ 
    public object Flurp(ICollection collection) { ... } 
    public object Flurp(ICloneable cloneable) { ... } 
} 

Quale si vince il premio? È un sovraccarico perfettamente valido, ma ICollection e ICloneable non hanno nulla a che fare l'uno con l'altro e Array implementa entrambi. Non riesco a vedere una soluzione ovvia qui.

Si ottiene solo peggio se iniziamo aggiungendo sovraccarichi per l'interfaccia stessa:

public interface ISuck 
{ 
    Stream Munge(ArrayList list); 
    Stream Munge(Hashtable ht); 
    string Munge(NameValueCollection nvc); 
    object Munge(IEnumerable enumerable); 
} 

public class HateHateHate : ISuck 
{ 
    public FileStream Munge(ICollection collection); 
    public NetworkStream Munge(IEnumerable enumerable); 
    public MemoryStream Munge(Hashtable ht); 
    public Stream Munge(ICloneable cloneable); 
    public object Munge(object o); 
    public Stream Munge(IDictionary dic); 
} 

Buona fortuna cercando di svelare questo mistero senza impazzire.

Ovviamente, tutto ciò è discutibile se si asserisce che le implementazioni dell'interfaccia dovrebbero supportare solo varianza di tipo restituito e non varianza di tipo argomento. Ma quasi tutti considererebbero una tale mezza implementazione completamente infranta e iniziare a inviare spam a segnalazioni di bug, quindi non credo che il team C# lo farà.

Non so se questo è il motivo ufficiale per cui non è supportato in C# oggi, ma dovrebbe servire come buon esempio del tipo di codice "solo scrittura" che potrebbe portare a, e parte di la filosofia progettuale del team C# è quella di cercare di impedire agli sviluppatori di scrivere codice terribile.

+0

D'accordo con voi punti Aaronaught. Potrebbe essere questo il motivo per cui Microsoft è andato in questo modo. – Amit

+1

Non credo che la maggior parte della gente penserebbe che una tale metà implementazione sia stata interrotta. C++ supporta la covarianza del tipo di ritorno ma non la controvarianza del tipo di parametro formale. Eiffel supporta la covarianza del tipo di ritorno e il tipo di parametro formale * covariance *, che è piuttosto strano. (Eiffel ha un approccio insolito alla sottotipizzazione.) Sather supporta entrambi. Di conseguenza, non ho visto una migrazione di massa da C++ a Sather. –

+0

@Eric: Non posso discutere con nessuno di questi (anche se potrei dire che la ragione per cui le persone non si affollano a Sather è perché non ne hanno mai sentito parlare: P), ma in un linguaggio come C# che * già * supporta la contravarianza di tipo argomento come parte della varianza di conversione del gruppo metodo, penso che la mancanza di quella caratteristica per l'implementazione dell'interfaccia si sentirebbe sicuramente infranta; Di regola, C# e .NET Framework cercano di essere coerenti. I miei due centesimi! – Aaronaught

2

Devi implementare metodi di un'interfaccia esattamente come sono nell'interfaccia. Clone di ICloneable il metodo restituisce un oggetto, quindi il tuo SomeClass deve anche restituire un oggetto. Puoi, comunque ehm, restituire un esempio SomeClass nel metodo Clone di SomeClass senza alcun problema, ma la definizione di un metodo deve corrispondere l'interfaccia:

public class SomeClass: IClonable 
{ 
    // Some Code 

    //Implementing interface method 
    Public object Clone() 
     { 
     SomeClass ret = new SomeClass(); 
     // copy date from this instance to ret 
     return ret; 
     } 
} 
+0

E quello sarebbe previsto. Con la nuova roba di .NET 4 dietro l'angolo con gli elenchi di in/out della varianza co/contr, è possibile fare esattamente quello che chiede? –

0

In base alle specifiche C#, è necessario utilizzare un metodo con una firma identica quando priorità assoluta o l'attuazione di un metodo di interfaccia. Tieni presente che Microsoft non possiede C#. Il loro compilatore C# è semplicemente la loro implementazione di esso. Quindi, perché le specifiche farebbero le cose in questo modo? Posso solo indovinare, ma ho il sospetto che fosse per facilità di implementazione.

1

In termini di spiegare le ragioni che stanno dietro C# decisioni, Eric Lippert da Microsoft ha scritto molto spiegando Contra/covarianza in C# ... ecco la lista di tag dal suo blog: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

[Edit] Specifico la tua domanda, questo potrebbe essere il post giusto .. http://blogs.msdn.com/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx

+0

Non proprio. Questi articoli riguardano la varianza di tipo parametrizzata. La domanda riguarda la varianza del tipo di ritorno, che è diversa. –

0

Sembra il tipo di cosa per cui avrebbero potuto usare i generici, ma sembra che ci sia una buona ragione per cui non l'hanno fatto.

se ne parla qui:

http://bytes.com/topic/c-sharp/answers/469671-generic-icloneable

In sostanza, una generica interfaccia che permetterebbe: public class MyClass : IClonable<MyClass>

sarebbe anche permettere: public class MyClass : IClonable<MyOtherClass>

che in realtà non fornisce qualsiasi vantaggio e potrebbe confondere le cose.

+0

Direi che IClonable sarebbe uno schema molto valido, specialmente se ereditava ISelf , che a sua volta includeva una proprietà "self" di sola lettura di tipo T. In molti casi, avrei i tipi che esponendo un metodo di clonazione pubblica dovrebbero non è ereditabile, ma dovrebbe avere un tipo di base identico ad eccezione di un metodo di clonazione protetto. In questo modo, "foo" e "derivedFoo" potevano entrambi avere derivati ​​non clonabili, ma una routine può essere scritta per accettare sia un CloneableFoo sia un CloneableDerivedFoo (poiché entrambi sono ICloneable ). – supercat

8

Mi permetta di riformulare la domanda:

linguaggi come C++ consentire un metodo assoluto di avere un tipo di ritorno più specifica rispetto al metodo override. Ad esempio, se abbiamo i tipi

abstract class Enclosure {} 
class Aquarium : Enclosure {} 
abstract class Animal 
{ 
    public virtual Enclosure GetEnclosure(); 
} 

allora questo non è legale in C#, ma il codice equivalente sarebbe legale in C++:

class Fish : Animal 
{ 
    public override Aquarium GetEnclosure() { ... 

Cos'è questa funzione di C++ chiamato?

La funzione è denominata "covarianza del tipo di ritorno". (Come indicato da un'altra risposta, sarebbe anche possibile supportare la "contrataglianza formale del tipo di parametro", sebbene C++ non lo sia.)

Perché non è supportato in C#?

Come ho sottolineato molte volte, non è necessario fornire un motivo per cui una funzione non è supportata; lo stato predefinito di tutte le funzionalità è "non supportato". È solo quando enormi quantità di tempo e sforzi sono messi nel realizzare un'implementazione che una funzionalità viene supportata. Piuttosto, le funzionalità che sono implementate devono avere motivi per loro, e per buona ragione, considerando quanto costa a farle.

Detto questo, ci sono due grandi "punti contro" questa caratteristica che sono le cose principali che impediscono di fare.

  1. Il CLR non lo supporta.Per fare questo lavoro, dovremmo fondamentalmente implementare il metodo di corrispondenza esatta e quindi creare un metodo di supporto che lo chiami. È fattibile ma dev'essere complicato.

  2. Anders ritiene che non si tratti di un linguaggio molto buono. Anders è l'architetto capo e se crede che sia una brutta caratteristica, le probabilità sono buone e non lo faranno. (Ora, badate bene, pensavamo che anche i parametri nominali e opzionali non valessero la pena, ma alla fine è stato fatto. A volte diventa chiaro che dovete stringere i denti e implementare una funzione che non vi piace l'estetica al fine di soddisfare la domanda del mondo reale.)

in breve, ci sono sicuramente momenti in cui sarebbe utile, e questa è una caratteristica spesso richiesto. Tuttavia, è improbabile che lo faremo. Il vantaggio della funzione non paga i suoi costi; esso complica notevolmente l'analisi semantica dei metodi e non abbiamo un modo veramente semplice per implementarlo.

+2

Quindi l'ovvia domanda di follow-up: * "Perché Anders pensa che la covarianza del tipo di ritorno sia una brutta caratteristica?" * – LBushkin

+1

@LBushkin: la prossima volta che lo vedi, chiediglielo. :-) –

Problemi correlati