Collection <T> versus List <T> que devez-vous utiliser sur vos interfaces?

162

Le code ressemble à ci-dessous:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Lorsque j'exécute l'analyse de code, j'obtiens la recommandation suivante.

Avertissement 3 CA1002: Microsoft.Design: Changez 'List' dans 'IMyClass.GetList ()' pour utiliser Collection, ReadOnlyCollection ou KeyedCollection

Comment résoudre ce problème et quelles sont les bonnes pratiques ici?

bovium
la source

Réponses:

234

Pour répondre à la partie «pourquoi» de la question de savoir pourquoi pas List<T>, les raisons sont la pérennité et la simplicité de l'API.

À l'épreuve du futur

List<T>n'est pas conçu pour être facilement extensible en le sous-classant; il est conçu pour être rapide pour les implémentations internes. Vous remarquerez que les méthodes ne sont pas virtuelles et ne peuvent donc pas être remplacées, et qu'il n'y a pas de hooks dans ses opérations Add/ Insert/ Remove.

Cela signifie que si vous avez besoin de modifier le comportement de la collection à l'avenir (par exemple pour rejeter les objets nuls que les gens essaient d'ajouter, ou pour effectuer un travail supplémentaire lorsque cela se produit, comme la mise à jour de l'état de votre classe), vous devez changer le type de la collection que vous retournez à celle que vous pouvez sous-classer, ce qui sera un changement d'interface de rupture (bien sûr, changer la sémantique de choses comme ne pas autoriser null peut également être un changement d'interface, mais des choses comme la mise à jour de l'état de votre classe interne ne le seraient pas).

Ainsi, en renvoyant soit une classe qui peut être facilement sous-classée telle que Collection<T>ou une interface telle que IList<T>, ICollection<T>soit IEnumerable<T>vous pouvez modifier votre implémentation interne pour qu'elle soit un type de collection différent pour répondre à vos besoins, sans casser le code des consommateurs car il peut toujours être retourné comme le type qu'ils attendent.

Simplicité de l'API

List<T>contient de nombreuses opérations utiles telles que BinarySearch, Sortetc. Cependant, s'il s'agit d'une collection que vous exposez, il est probable que vous contrôliez la sémantique de la liste, et non les consommateurs. Ainsi, même si votre classe en interne peut avoir besoin de ces opérations, il est très peu probable que les consommateurs de votre classe veuillent (ou même devraient) les appeler.

En tant que tel, en offrant une classe ou une interface de collection plus simple, vous réduisez le nombre de membres que les utilisateurs de votre API voient et leur facilitez l'utilisation.

Greg Beech
la source
1
Cette réponse est morte. Une autre bonne lecture sur le sujet peut être trouvée ici: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
7
Je vois vos premiers points, mais je ne sais pas si je suis d'accord sur la partie simplicité de votre API.
Boris Callens
stackoverflow.com/a/398988/2632991 Voici également un très bon article sur la différence entre les collections et les listes.
El Mac
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Est mort. Avons-nous une information plus récente à ce sujet?
Machado
50

Je le déclarerais personnellement pour renvoyer une interface plutôt qu'une collection concrète. Si vous voulez vraiment accéder à la liste, utilisez IList<T>. Sinon, considérez ICollection<T>et IEnumerable<T>.

Jon Skeet
la source
1
Est-ce que IList étendrait alors l'interface ICollection?
bovium
8
@Jon: Je sais que c'est vieux, mais pouvez-vous commenter ce que dit Krzysztof sur blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? Plus précisément, son commentaire, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002, semble aller de pair avec les commentaires de Krzysztof. Je ne peux pas imaginer pourquoi une collection concrète serait recommandée au lieu d'une interface, et pourquoi la distinction entre entrées / sorties.
Nelson Rothermel
3
@Nelson: Il est rare que vous souhaitiez obliger les appelants à passer dans une liste immuable, mais il est raisonnable d'en renvoyer une pour qu'ils sachent qu'elle est définitivement immuable. Pas sûr des autres collections cependant. Ce serait bien d'avoir plus de détails.
Jon Skeet
2
Ce n'est pas pour un cas particulier. De toute évidence, en général, cela ReadOnlyCollection<T>n'aurait pas de sens pour une entrée. De même, IList<T>comme le dit une entrée, "j'ai besoin de Sort () ou d'un autre membre qu'un IList a", ce qui n'a aucun sens pour une sortie. Mais ce que je voulais dire, c'est pourquoi serait ICollection<T>recommandé comme entrée et Collection<T>comme sortie. Pourquoi ne pas l' utiliser ICollection<T>comme sortie également comme vous l'avez suggéré?
Nelson Rothermel
9
Je pense que cela a à voir avec l'absence d'ambiguïté. Collection<T>et les ReadOnlyCollection<T>deux dérivent de ICollection<T>(c'est-à-dire qu'il n'y en a pas IReadOnlyCollection<T>). Si vous retournez l'interface, il n'est pas évident de savoir laquelle il s'agit et si elle peut être modifiée. En tout cas, merci pour ta contribution. C'était un bon test de santé mentale pour moi.
Nelson Rothermel
3

Je ne pense pas que quiconque ait encore répondu à la partie «pourquoi» ... alors voilà. La raison "pour laquelle" vous "devriez" utiliser a Collection<T>au lieu de a List<T>est que si vous exposez a List<T>, toute personne ayant accès à votre objet peut modifier les éléments de la liste. Attendu qu'il Collection<T>est censé indiquer que vous créez vos propres méthodes "Add", "Remove", etc.

Vous n'avez probablement pas à vous en soucier, car vous codez probablement l'interface pour vous-même uniquement (ou peut-être pour quelques collègues). Voici un autre exemple qui pourrait avoir du sens.

Si vous avez un tableau public, ex:

public int[] MyIntegers { get; }

On pourrait penser que parce qu'il n'y a qu'un accesseur "get", personne ne peut jouer avec les valeurs, mais ce n'est pas vrai. Tout le monde peut changer les valeurs à l'intérieur comme ceci:

someObject.MyIngegers[3] = 12345;

Personnellement, j'utiliserais juste List<T>dans la plupart des cas. Mais si vous concevez une bibliothèque de classes que vous allez donner à des développeurs aléatoires, et que vous devez vous fier à l'état des objets ... alors vous voudrez créer votre propre collection et la verrouiller à partir de là: )

Timothy Khouri
la source
"Si vous renvoyez List <T> au code client, vous ne pourrez jamais recevoir de notifications lorsque le code client modifie la collection." - FX COP ... Voir aussi " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... wow, il semble que je ne sois pas hors de la base après tout :)
Timothy Khouri
Wow - des années plus tard, je vois que le lien que j'ai collé dans le commentaire ci-dessus contenait une citation à la fin, donc cela n'a pas fonctionné ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri
2

Il s'agit principalement d'abstraire vos propres implémentations au lieu d'exposer l'objet List à manipuler directement.

Il n'est pas conseillé de laisser d'autres objets (ou personnes) modifier directement l'état de vos objets. Pensez aux getters / setters de propriété.

Collection -> Pour la collection normale
ReadOnlyCollection -> Pour les collections qui ne doivent pas être modifiées
KeyedCollection -> Lorsque vous voulez des dictionnaires à la place.

La façon de résoudre ce problème dépend de ce que vous voulez que votre classe fasse et de l'objectif de la méthode GetList (). Peux-tu élaborer?

chakrit
la source
1
Mais Collection <T> et KeyedCollection <,> ne sont pas non plus en lecture seule.
nawfal
@nawfal Et où ai-je dit que c'était?
chakrit
1
Vous déclarez ne pas laisser d'autres objets (ou personnes) modifier directement l'état de vos objets et lister 3 types de collections. Sauf ReadOnlyCollectionles deux autres, cela ne lui obéit pas.
nawfal
Cela fait référence aux «bonnes pratiques». Bon contexte s'il vous plaît. La liste ci-dessous énonce simplement les exigences de base de ces types puisque l'OP veut comprendre l'avertissement. Je vais ensuite lui poser des questions sur le but d’ GetList()aider à mieux l’aider.
chakrit le
1
Ok bien je comprends cette partie. Mais le raisonnement n'est toujours pas valable selon moi. Vous dites Collection<T>aide à résumer l'implémentation interne et empêche la manipulation directe de la liste interne. Comment? Collection<T>est simplement un wrapper, fonctionnant sur la même instance passée. C'est une collection dynamique destinée à l'héritage, rien d'autre (la réponse de Greg est plus pertinente ici).
nawfal
1

Dans ce genre de cas, j'essaie généralement d'exposer le moins d'implémentation nécessaire. Si les consommateurs n'ont pas besoin de savoir que vous utilisez réellement une liste, vous n'avez pas besoin de renvoyer une liste. En retournant comme Microsoft suggère une collection, vous masquez le fait que vous utilisez une liste des consommateurs de votre classe et les isolez contre un changement interne.

Harald Scheirich
la source
1

Quelque chose à ajouter même si cela fait longtemps que cela n'a pas été demandé.

Lorsque votre type de liste dérive de List<T> au lieu de Collection<T>, vous ne pouvez pas implémenter les méthodes virtuelles protégées qui Collection<T>implémentent. Cela signifie que votre type dérivé ne peut pas répondre au cas où des modifications seraient apportées à la liste. Cela est dû au fait que List<T>vous en êtes conscient lorsque vous ajoutez ou supprimez des éléments. Être capable de répondre aux notifications est une surcharge et List<T>ne l'offre donc pas.

Dans les cas où un code externe a accès à votre collection, vous ne pouvez pas contrôler le moment où un élément est ajouté ou supprimé. Collection<T>Fournit donc un moyen de savoir quand votre liste a été modifiée.

NullReference
la source
0

Je ne vois aucun problème à renvoyer quelque chose comme

this.InternalData.Filter(crteria).ToList();

Si j'ai renvoyé une copie déconnectée de données internes ou un résultat détaché d'une requête de données, je peux retourner en toute sécuritéList<TItem> sans exposer aucun détail d'implémentation et permettre d'utiliser les données renvoyées de manière pratique.

Mais cela dépend du type de consommateur IEnumerable<TItem> auquel j'attends - si c'est quelque chose comme une grille de données, je préfère retourner, qui sera la liste des éléments copiée de toute façon dans la plupart des cas :)

Konstantin Isaev
la source
-1

Eh bien, la classe Collection n'est en réalité qu'une classe wrapper autour d'autres collections pour masquer leurs détails d'implémentation et d'autres fonctionnalités. Je pense que cela a quelque chose à voir avec la propriété masquant le modèle de codage dans les langages orientés objet.

Je pense que vous ne devriez pas vous en soucier, mais si vous voulez vraiment plaire à l'outil d'analyse de code, procédez comme suit:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Tamas Czinege
la source
1
Désolé, c'était une faute de frappe. Je menat Collection <MyClass>. J'ai vraiment hâte de mettre la main sur C # 4 et la covariance générique btw!
Tamas Czinege
L'emballage dans un Collection<T>ne protège ni lui-même ni la collection sous-jacente pour autant que je sache.
nawfal
Ce morceau de code était de "plaire à l'outil d'analyse de code". Je ne pense pas que @TamasCzinege ait dit nulle part que l'utilisation Collection<T>protégera immédiatement votre collection sous-jacente.
chakrit