67 votes

Pourquoi (fait-il vraiment?) La liste <T> implémenter toutes ces interfaces, pas seulement IList <T> ?

List déclaration de MSDN:

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

Le réflecteur donne une image similaire. Est-ce que List implémente réellement tous ces éléments (si oui pourquoi)? J'ai vérifié:

     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;
    }
 

il compile.

[MIS À JOUR]:

Les gars, si je comprends bien, toutes les réponses restent que:

 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();
    }
}
 

donnerait une erreur, mais il compile et fonctionne sans aucune erreur. Pourquoi?

130voto

Eric Lippert Points 300275

Mise à JOUR: Cette question a été la base de mon entrée de blog pour le lundi 4 avril 2011. Merci pour la grande question.

Permettez-moi de le décomposer en plusieurs petites questions.

N' List<T> vraiment mettre en œuvre toutes ces interfaces?

Oui.

Pourquoi?

Parce que quand une interface (par exemple, IList<T>) hérite d'une interface (disons IEnumerable<T>) puis la mise en œuvre de la plus dérivée de l'interface sont nécessaires pour mettre en œuvre la moins dérivé de l'interface. C'est ce que l'héritage de l'interface moyens; si vous remplissez le contrat de la plus type dérivé ensuite, vous devez également remplir le contrat de moins de type dérivé.

Si une classe doit implémenter toutes les méthodes de toutes les interfaces dans la fermeture transitive de ses interfaces de base?

Exactement.

Est une classe qui implémente une interface dérivée également tenu d'indiquer dans son type de base de la liste que c'est la mise en œuvre de l'ensemble de ceux qui sont moins dérivées des interfaces?

Pas de.

C'est la classe doivent PAS état?

Pas de.

Il est donc facultatif si la moins-dérivée de la mise en œuvre des interfaces sont indiquées dans le type de base de la liste?

Oui.

Toujours?

Presque toujours:

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

Elle est facultative si I3 états qu'il hérite de I1.

class B : I3 {}

La mise en œuvre de I3 sont nécessaires pour mettre en œuvre I2 et I1, mais ils ne sont pas tenus d'indiquer explicitement qu'ils le font. C'est facultatif.

class D : B {}

Les classes dérivées ne sont pas nécessaires à la re-état de la mise en œuvre d'une interface à partir de leur classe de base, mais sont autorisés à le faire. (Ce cas est spécial; voir ci-dessous pour plus de détails.)

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

Les arguments de Type correspondant à T et U sont nécessaires pour mettre en œuvre I2 et I1, mais elle est facultative pour les contraintes sur T ou U pour l'état.

Il est toujours l'option de re-état de base de l'interface dans une classe partielle:

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

La seconde moitié de E est autorisé à déclarer qu'il met en œuvre I3 et I2 ou I1, mais pas obligé de le faire.

OK, je l'obtenir; il est facultatif. Pourquoi quelqu'un inutilement de l'état d'une interface de base?

Peut-être parce qu'ils croient que cela rend le code plus facile à comprendre et plus l'auto-documentation.

Ou, peut-être le développeur écrit le code

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

et le réalisé, oh, attendez une minute, I2 devrait hériter de I1. Pourquoi devrait faire que modifier, puis exiger que le promoteur de revenir en arrière et modifier la déclaration de I3 à ne pas contenir la mention explicite de I1? Je ne vois aucune raison de forcer les développeurs à supprimer les informations redondantes.

En plus d'être plus facile à lire et à comprendre, est-il une technique différence entre indiquant une interface explicitement dans le type de base de la liste et de le laisser sous silence mais implicite?

Généralement non, mais il peut y avoir une différence subtile dans un cas. Supposons que vous avez une classe dérivée de D dont la classe de base B a mis en œuvre certaines interfaces. D implémente automatiquement les interfaces via B. Si vous re-état des interfaces en D pour la classe de base de la liste puis le compilateur C# va faire une interface de mise en œuvre. Les détails sont un peu subtile; si vous êtes intéressé par la façon dont cela fonctionne, alors je vous recommande une lecture attentive de l'article 13.4.6 du C# 4 spécification.

L' List<T> code source fait état de toutes les interfaces?

Pas de. Le code source dit

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

Pourquoi ne MSDN avoir l'interface complète de la liste, mais le vrai code source ne l'est pas?

MSDN est de la documentation; il est censé vous donner autant d'informations que vous pourriez vouloir. Il est beaucoup plus clair pour que la documentation soit complète en une seule place que de faire de la recherche à travers une dizaine de pages pour trouver ce que l'interface de jeu est.

Pourquoi ne Réflecteur afficher toute la liste?

Réflecteur a seulement des métadonnées à partir de ce point. Depuis la mise en la liste complète est facultatif, Réflecteur n'a aucune idée de savoir si le code source d'origine contient la liste complète ou pas. Il est préférable de pécher par excès de plus d'informations. Encore une fois, le Réflecteur est de tenter de vous aider en vous montrant plus d'informations plutôt que de cacher les informations dont vous pourriez avoir besoin.

QUESTION BONUS: Pourquoi est - IEnumerable<T> hériter d' IEnumerable mais IList<T> n'hérite pas de IList?

Une séquence d'entiers peut être traitée comme une séquence d'objets, par la boxe tout entier qu'il sort de la séquence. Mais une lecture-écriture de la liste d'entiers ne peut être traitée comme un lire-écrire la liste des objets, parce que vous pouvez mettre une chaîne en lecture-écriture à la liste des objets. Un IList<T> n'est pas tenu de s'acquitter de l'ensemble du contrat d' IList, de sorte qu'il n'hérite pas de lui.

5voto

Ilya Kogan Points 7357

Les interfaces non génériques sont destinées à la compatibilité ascendante. Si vous écrivez du code en utilisant des génériques et souhaitez passer votre liste à un module écrit en .NET 1.0 (qui ne contient pas de génériques), vous voulez toujours que cela réussisse. D'où IList, ICollection, IEnumerable .

5voto

Danny Chen Points 14781

Grande question et la grande réponse de Eric Lippert. Cette question est venue à mon esprit dans le passé, et ce qui suit est ma compréhension, j'espère qu'elle vous aide.

Supposons que je suis un programmeur C# dans une autre planète dans l'univers, dans cette planète, tous les animaux peuvent voler. Donc mon programme ressemble à:

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

Eh bien, vous peut-être confus, depuis Birds sont Animals, et tous Animals peut voler, pourquoi avons-nous besoin de spécifier IFlyable dans l'interface IBird? OK, nous allons le modifier:

interface IBird : IAnimal
{
}

Je suis sûr à 100% que le programme fonctionne comme avant, rien n'est donc changé? L' IFlyable dans l'interface IBird est totalement inutile? Let's go sur.

Un jour, mon entreprise a vendu le logiciel à une autre société sur la Terre. Mais sur la Terre, et non tous les animaux peuvent voler! Alors bien sûr, nous avons besoin de modifier l'interface IAnimal et les classes qui l'implémentent. Après la modification, j'ai trouvé qu' IBird est incorrect maintenant parce que les oiseaux de la terre peut voler! Maintenant je regrette la suppression IFlyable de IBird!

3voto

Kevin Points 57797
  1. Oui, car List<T> can a les propriétés et la méthode pour remplir toutes ces interfaces. Vous ne savez pas quelle interface quelqu'un va avoir déclarée comme paramètre ou valeur de retour. Par conséquent, plus la liste d'interfaces implémentées est importante, plus elle peut être polyvalente.
  2. Votre code sera compilé car la conversion ascendante ( var a1 = (I1)a; ) échoue au moment de l'exécution, pas lors de la compilation. Je peux faire var a1 = (int)a et le faire compiler.

2voto

Yaakov Ellis Points 15470

List<> implémente toutes ces interfaces afin d'exposer les fonctionnalités décrites par les différentes interfaces. Il englobe les fonctionnalités d'une liste générique, d'une collection et d'un énumérable (avec une compatibilité ascendante avec les équivalents non génériques)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X