2011-01-27 16 views
66

List dichiarazione da MSDN:Perché l'elenco (<T> implementa tutte queste interfacce, non solo IList <T>?

public class List<T> : IList<T>, ICollection<T>, 
IEnumerable<T>, IList, ICollection, IEnumerable 

riflettore dà un'immagine simile. List implementa davvero tutti questi (se sì perché)? Ho controllato:

interface I1 {} 
    interface I2 : I1 {} 
    interface I3 : I2 {} 

    class A : I3 {} 
    class B : I3, I2, I1 {} 

    static void Main(string[] args) 
    { 
     var a = new A(); 
     var a1 = (I1)a; 
     var a2 = (I2)a; 
     var a3 = (I3)a; 

     var b = new B(); 
     var b1 = (I1) b; 
     var b2 = (I2)b; 
     var b3 = (I3)b; 
    } 

si compila.

[Aggiornamento]:

Ragazzi, mi pare di capire, tutte le risposte restano che:

class Program 
{ 

    interface I1 {} 
    interface I2 : I1 {} 
    interface I3 : I2 {} 

    class A : I3 {} 
    class B : I3, I2, I1 {} 

    static void I1M(I1 i1) {} 
    static void I2M(I2 i2) {} 
    static void I3M(I3 i3) {} 

    static void Main(string[] args) 
    { 
     var a = new A(); 
     I1M(a); 
     I2M(a); 
     I3M(a); 

     var b = new B(); 
     I1M(b); 
     I2M(b); 
     I3M(b); 

     Console.ReadLine(); 
    } 
} 

darebbe errori, ma compilato ed eseguito senza errori. Perché?

+2

Sì Lista implementa tutte quelle interfacce, perché - perché sono tutte rilevanti per una raccolta di elenchi. A cosa serve il tuo codice? È una domanda diversa? –

+0

Il tuo codice di esempio non ha nulla a che fare con la Lista 'classe' ... – BoltClock

+0

Penso che sta chiedendo il motivo per cui 'Lista pubblica di classe : IList , ICollection , IEnumerable , etc.' piuttosto che' Lista public class : IList '. Si noti che 'ICollection' e' IList' (quelli non generici) non sono ereditati da 'IList '. – Rup

risposta

129

AGGIORNAMENTO: questa domanda era la base di my blog entry for Monday April 4th 2011. Grazie per l'ottima domanda.

Lasciarlo scomporre in molte domande più piccole.

Il List<T> implementa davvero tutte quelle interfacce?

Sì.

Perché?

Poiché quando un'interfaccia (ad esempio, IList<T>) eredita da un'interfaccia (diciamo IEnumerable<T>) allora implementatori dell'interfaccia più derivato devono attuare anche l'interfaccia meno derivata. Questo è ciò che significa l'ereditarietà dell'interfaccia; se si adempie il contratto del tipo più derivato, allora si richiede anche di adempiere il contratto del tipo meno derivato.

Quindi una classe è necessaria per implementare tutti i metodi di tutte le interfacce nella chiusura transitiva delle sue interfacce di base?

Esattamente.

È una classe che implementa un'interfaccia più derivata anche per indicare nella sua lista di tipi di base che sta implementando tutte quelle interfacce meno derivate?

No.

è la classe necessaria per NON affermarlo?

No.

Quindi è opzionale se le interfacce implementate meno-derivati ​​sono indicati nella lista di tipo di base?

Sì.

Sempre?

Quasi sempre:

interface I1 {} 
interface I2 : I1 {} 
interface I3 : I2 {} 

E 'facoltativo se I3 afferma che eredita da I1.

class B : I3 {} 

Implementers di I3 sono tenuti ad attuare I2 e I1, ma non sono tenuti a dichiarare esplicitamente che lo stanno facendo. È facoltativo

class D : B {} 

classi derivate non sono tenuti a ri-stato che implementano l'interfaccia dalla loro classe di base, ma sono autorizzati a farlo. (Questo caso è speciale, vedi sotto per maggiori dettagli.)

class C<T> where T : I3 
{ 
    public virtual void M<U>() where U : I3 {} 
} 

tipo argomenti corrispondente a T e U sono necessari per implementare I2 e I1, ma è facoltativo per i vincoli T o U per affermare che.

È sempre facoltativo di ri-stato qualsiasi interfaccia di base in una classe parziale:

partial class E : I3 {} 
partial class E {} 

La seconda metà di E è consentito affermare che implementa I3 o I2 o I1, ma non necessario per così.

OK, ho capito; è facoltativo. Perché qualcuno dovrebbe indicare inutilmente un'interfaccia di base?

Forse perché credono che farlo rende il codice più facile da capire e più autodocumentante.

O, forse lo sviluppatore ha scritto il codice come

interface I1 {} 
interface I2 {} 
interface I3 : I1, I2 {} 

e si rese conto, oh, aspetta un minuto, I2 dovrebbe ereditare da I1.Perché la modifica dovrebbe richiedere allo sviluppatore di tornare indietro e modificare la dichiarazione di I3 a non con menzione esplicita di I1? Non vedo alcun motivo per costringere gli sviluppatori a rimuovere le informazioni ridondanti.

Oltre ad essere più facile da leggere e capire, c'è qualche tecnica differenza tra affermando un'interfaccia in modo esplicito nella lista tipo di base e lasciando non dichiarato ma implicito?

Di solito no, ma in un caso può esserci una sottile differenza. Supponiamo di avere una classe derivata D la cui classe base B ha implementato alcune interfacce. D implementa automaticamente tali interfacce tramite B. Se si re-dichiarano le interfacce nella lista delle classi di base di D, il compilatore C# eseguirà una nuova implementazione dell'interfaccia . I dettagli sono un po 'sottili; se sei interessato a come funziona allora ti consiglio una lettura attenta della sezione 13.4.6 della specifica C# 4.

Il codice sorgente List<T> indica effettivamente tutte quelle interfacce?

No. Il codice sorgente dice

public class List<T> : IList<T>, System.Collections.IList 

Perché MSDN avere la lista completa interfaccia ma il codice sorgente reale non?

Perché MSDN è documentazione; dovrebbe fornire tutte le informazioni che desideri. È molto più chiaro che la documentazione sia completa in un unico posto piuttosto che farti cercare tra dieci diverse pagine per scoprire quale sia l'intero set di interfacce.

Perché Reflector mostra l'intera lista?

Riflettore ha solo metadati da cui lavorare. Poiché l'inserimento nell'elenco completo è facoltativo, Reflector non ha idea se il codice sorgente originale contenga o meno l'elenco completo. È meglio sbagliare dalla parte di maggiori informazioni. Di nuovo, Reflector sta tentando di aiutarti mostrandoti più informazioni piuttosto che nascondendo le informazioni di cui potresti aver bisogno.

BONUS Domanda: Perché IEnumerable<T> ereditano da IEnumerable ma IList<T> non ereditare da IList?

Una sequenza di numeri interi può essere trattata come una sequenza di oggetti, eseguendo il pugilato di ogni numero intero come esce dalla sequenza. Ma un elenco di interi in lettura e scrittura non può essere considerato come un elenco di oggetti in lettura-scrittura, perché è possibile inserire una stringa in un elenco di oggetti in lettura e scrittura. Un IList<T> non è tenuto a soddisfare l'intero contratto di IList, quindi non eredita da esso.

+0

ingrandito. La domanda bonus è stata posta quest'anno: [[Perché il generico IList <> non eredita IList non generico] (http://stackoverflow.com/questions/14559070/why-generic-ilist-does-not-inherit-non- generic-ilist)] –

3
  1. Sì, perché List<T> può avere le proprietà e il metodo per soddisfare tutte quelle interfacce. Non si sa quale interfaccia verrà dichiarata come parametro o come valore di ritorno, quindi più interfacce vengono implementate, più è versatile.
  2. Il tuo codice verrà compilato perché upcasting (var a1 = (I1)a;) non riesce in fase di runtime non compile tempo. Posso fare var a1 = (int)a e farlo compilare.
+0

sembra funzionare ... ho aggiunto console.readline fino alla fine e ha ottenuto il mio input e terminato senza errori – idm

+0

Verrà eseguito correttamente. Cosa ti fa pensare che non lo farebbe? –

5

Le interfacce non generiche sono compatibili con le versioni precedenti. Se scrivi codice usando generici e vuoi passare la tua lista ad un modulo scritto in .NET 1.0 (che non ha generici), vuoi comunque che questo abbia successo. Quindi IList, ICollection, IEnumerable.

2

List<> implementa tutte queste interfacce per esporre la funzionalità descritta dalle diverse interfacce. Comprende le caratteristiche di un elenco generico, raccolta ed enumerabile (con retrocompatibilità agli equivalenti non generici)

5

Ottima domanda e ottima risposta di Eric Lippert. Questa domanda mi è venuta in mente in passato, e quanto segue è la mia comprensione, spero che ti aiuti.

Supponiamo che io sia un programmatore C# in un altro pianeta dell'universo, in questo pianeta tutti gli animali possono volare.Quindi il mio programma si presenta come:

interface IFlyable 
{ 
    void Fly(); 
} 
interface IAnimal : IFlyable 
{ 
    //something 
} 
interface IBird : IAnimal, IFlyable 
{ 
} 

Ebbene si può essere confuso, dal momento che sono BirdsAnimals, e tutto Animals possono volare, perché abbiamo bisogno di specificare IFlyable nell'interfaccia IBird? OK Cambiamo a:

interface IBird : IAnimal 
{ 
} 

che sto al 100% che il programma funziona come prima, quindi nulla è cambiato? Il IFlyable nell'interfaccia IBird è totalmente inutile? Andiamo avanti.

Un giorno, la mia azienda ha venduto il software a un'altra azienda sulla Terra. Ma sulla Terra, non tutti gli animali possono volare! Quindi, ovviamente, dobbiamo modificare l'interfaccia IAnimal e le classi che la implementano. Dopo la modifica, ho trovato che IBird non è corretto ora perché gli uccelli in terra possono volare! Ora mi pento di aver rimosso IFlyable da IBird!

+0

Err ... +1 per il Fly –

+0

Tranne che la risposta di Eric dice che il _source code_ in realtà ha solo l'equivalente di IBird: IAnimal, e che l'IFlyable appare solo in MSDN, ILSpy, etc, come aggiuntivo (dedotto) informazioni per aiutarti. –

+0

Ma poi devi passare attraverso tutte le altre interfacce 'IDog',' ICat', 'IRhinoceros', etc, che presumibilmente hai aggiunto' IFlyable' nell'universo di tutti gli animaletti e * rimuovi * IFlyable da loro. Ad ogni modo, hai un sacco di aggiornamenti da fare. – Blorgbeard

Problemi correlati