Sammlung <T> versus Liste <T> Was sollten Sie auf Ihren Schnittstellen verwenden?

162

Der Code sieht wie folgt aus:

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

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

Wenn ich die Code-Analyse durchführe, erhalte ich die folgende Empfehlung.

Warnung 3 CA1002: Microsoft.Design: Ändern Sie 'Liste' in 'IMyClass.GetList ()', um Collection, ReadOnlyCollection oder KeyedCollection zu verwenden

Wie soll ich das beheben und was ist hier eine gute Praxis?

Rinder
quelle

Antworten:

234

Um den "Warum" -Teil der Frage zu beantworten, warum nicht List<T>, sind die Gründe zukunftssicher und API-Einfachheit.

Zukunftssicher

List<T>ist nicht so konzipiert, dass es durch Unterklassen leicht erweiterbar ist; Es ist so konzipiert, dass es für interne Implementierungen schnell ist. Sie werden bemerken , die Methoden auf nicht virtuell sind und so nicht außer Kraft gesetzt werden kann, und es gibt keine Haken in seine Add/ Insert/ RemoveOperationen.

Dies bedeutet, dass Sie den Typ ändern müssen, wenn Sie das Verhalten der Auflistung in Zukunft ändern müssen (z. B. um Nullobjekte abzulehnen, die von Personen hinzugefügt werden sollen, oder um zusätzliche Arbeiten auszuführen, wenn dies geschieht, z. B. das Aktualisieren Ihres Klassenstatus) Von der Sammlung kehren Sie zu einer Unterklasse zurück, die eine brechende Schnittstellenänderung darstellt (natürlich kann das Ändern der Semantik von Dingen wie dem Nichtzulassen von Null auch eine Schnittstellenänderung sein, aber Dinge wie das Aktualisieren Ihres internen Klassenstatus wären dies nicht).

Wenn Sie also entweder eine Klasse zurückgeben, die leicht in Unterklassen wie Collection<T>oder eine Schnittstelle wie unterteilt werden kann IList<T>, ICollection<T>oder IEnumerable<T>Ihre interne Implementierung so ändern können, dass sie Ihren Anforderungen entspricht, ohne den Code der Verbraucher zu beschädigen, da sie weiterhin als zurückgegeben werden kann der Typ, den sie erwarten.

API-Einfachheit

List<T>enthält viele nützliche Funktionen wie BinarySearch, Sortund so weiter. Wenn es sich jedoch um eine Sammlung handelt, die Sie verfügbar machen, steuern Sie wahrscheinlich die Semantik der Liste und nicht die Verbraucher. Während Ihre Klasse diese Operationen möglicherweise intern benötigt, ist es sehr unwahrscheinlich, dass Verbraucher Ihrer Klasse sie anrufen möchten (oder sollten).

Indem Sie eine einfachere Sammlungsklasse oder -schnittstelle anbieten, reduzieren Sie die Anzahl der Mitglieder, die Benutzer Ihrer API sehen, und erleichtern ihnen die Verwendung.

Greg Beech
quelle
1
Diese Antwort ist tot auf. Eine weitere gute Lektüre zu diesem Thema finden Sie hier: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
7
Ich sehe Ihre ersten Punkte, aber ich weiß nicht, ob ich Ihrem API-Einfachheitsteil zustimme.
Boris Callens
stackoverflow.com/a/398988/2632991 Hier ist auch ein wirklich guter Beitrag über den Unterschied zwischen Sammlungen und Listen.
El Mac
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Ist tot. Haben wir dafür neuere Informationen?
Machado
50

Ich würde persönlich erklären, dass es sich eher um eine Schnittstelle als um eine konkrete Sammlung handelt. Wenn Sie wirklich Listenzugriff wünschen, verwenden Sie IList<T>. Andernfalls berücksichtigen Sie ICollection<T>und IEnumerable<T>.

Jon Skeet
quelle
1
Würde der IList dann die ICollection-Schnittstelle erweitern?
Bovium
8
@ Jon: Ich weiß, dass dies alt ist, aber können Sie kommentieren, was Krzysztof unter blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx sagt ? Insbesondere sein Kommentar 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 scheint mit Krzysztofs Kommentaren übereinzustimmen. Ich kann mir nicht vorstellen, warum eine konkrete Sammlung anstelle einer Schnittstelle empfohlen wird und warum die Unterscheidung zwischen Ein- / Ausgängen.
Nelson Rothermel
3
@Nelson: Es ist selten , dass Sie möchten , benötigen Anrufer in einer unveränderlichen Liste zu übergeben, aber es ist vernünftig zurückgeben ein , so dass sie wissen , dass es auf jeden Fall unveränderlich ist. Ich bin mir jedoch nicht sicher über die anderen Sammlungen. Wäre schön, mehr Details zu haben.
Jon Skeet
2
Es ist nicht für einen bestimmten Fall. Offensichtlich ReadOnlyCollection<T>würde eine Eingabe im Allgemeinen keinen Sinn ergeben. Ebenso, IList<T>wie eine Eingabe sagt, "Ich brauche Sort () oder ein anderes Mitglied, das eine IList hat", was für eine Ausgabe keinen Sinn ergibt. Aber ich meinte, warum ICollection<T>sollte als Eingabe und Collection<T>als Ausgabe empfohlen werden. Warum nicht auch ICollection<T>als Ausgabe verwenden, wie Sie vorgeschlagen haben?
Nelson Rothermel
9
Ich denke, das hat mit Eindeutigkeit zu tun. Collection<T>und ReadOnlyCollection<T>beide stammen von ICollection<T>(dh es gibt keine IReadOnlyCollection<T>). Wenn Sie die Schnittstelle zurückgeben, ist nicht klar, um welche es sich handelt und ob sie geändert werden kann. Trotzdem danke für deine Eingabe. Dies war eine gute Überprüfung der geistigen Gesundheit für mich.
Nelson Rothermel
3

Ich glaube, noch hat niemand den "Warum" -Teil beantwortet ... also los geht's. Der Grund, warum "Sie" a Collection<T>anstelle von a verwenden sollten, List<T>liegt darin, dass List<T>jeder, der Zugriff auf Ihr Objekt erhält, die Elemente in der Liste ändern kann, wenn Sie a verfügbar machen . Während Collection<T>soll anzeigen, dass Sie Ihre eigenen Methoden "Hinzufügen", "Entfernen" usw. erstellen.

Sie müssen sich wahrscheinlich keine Sorgen machen, da Sie die Benutzeroberfläche wahrscheinlich nur für sich selbst (oder vielleicht für einige Kollegen) codieren. Hier ist ein weiteres Beispiel, das Sinn machen könnte.

Wenn Sie ein öffentliches Array haben, z.

public int[] MyIntegers { get; }

Sie würden denken, dass, weil es nur einen "get" -Zugriff gibt, niemand mit den Werten herumspielen kann, aber das ist nicht wahr. Jeder kann die Werte dort einfach so ändern:

someObject.MyIngegers[3] = 12345;

Persönlich würde ich List<T>in den meisten Fällen nur verwenden . Wenn Sie jedoch eine Klassenbibliothek entwerfen, die Sie an zufällige Entwickler weitergeben möchten, und sich auf den Status der Objekte verlassen müssen, sollten Sie Ihre eigene Sammlung erstellen und von dort aus sperren: )

Timothy Khouri
quelle
"Wenn Sie List <T> an den Clientcode zurückgeben, können Sie niemals Benachrichtigungen erhalten, wenn der Clientcode die Sammlung ändert." - FX COP ... Siehe auch " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... wow, ich bin doch nicht von der Basis :)
Timothy Khouri
Wow - Jahre später sehe ich, dass der Link, den ich in den obigen Kommentar eingefügt habe, am Ende ein Zitat hatte, also hat es nicht funktioniert ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri
2

Es geht hauptsächlich darum, Ihre eigenen Implementierungen zu abstrahieren, anstatt das List-Objekt, das direkt bearbeitet werden soll, verfügbar zu machen.

Es ist keine gute Praxis, andere Objekte (oder Personen) den Status Ihrer Objekte direkt ändern zu lassen. Denken Sie an Getter / Setter.

Sammlung -> Für normale Sammlung
ReadOnlyCollection -> Für Sammlungen, die nicht geändert werden sollten
KeyedCollection -> Wenn Sie stattdessen Wörterbücher möchten.

Wie Sie das Problem beheben können, hängt davon ab, was Ihre Klasse tun soll und welchen Zweck die GetList () -Methode hat. Können Sie das näher erläutern?

Chakrit
quelle
1
Collection <T> und KeyedCollection <,> sind jedoch auch nicht schreibgeschützt.
Nawfal
@nawfal Und wo habe ich gesagt, dass es ist?
Chakrit
1
Sie geben an , dass andere Objekte (oder Personen) den Status Ihrer Objekte nicht direkt ändern dürfen, und listen 3 Sammlungstypen auf. Abgesehen von ReadOnlyCollectionden anderen beiden gehorcht es nicht.
Nawfal
Das bezieht sich auf "gute Praxis". Richtiger Kontext bitte. In der folgenden Liste sind lediglich die Grundanforderungen für solche Typen aufgeführt, da das OP die Warnung verstehen möchte. Ich frage ihn dann nach dem Zweck GetList(), ihm besser helfen zu können.
Chakrit
1
Ok gut, ich verstehe diesen Teil. Trotzdem ist der Argumentationsteil meiner Meinung nach nicht richtig. Sie sagen, es Collection<T>hilft bei der Abstraktion der internen Implementierung und verhindert die direkte Manipulation der internen Liste. Wie? Collection<T>ist lediglich ein Wrapper, der mit derselben übergebenen Instanz arbeitet. Es ist eine dynamische Sammlung, die für die Vererbung gedacht ist, sonst nichts (Gregs Antwort ist hier relevanter).
Nawfal
1

In solchen Fällen versuche ich normalerweise, die geringste erforderliche Implementierungsmenge bereitzustellen. Wenn die Verbraucher nicht wissen müssen, dass Sie tatsächlich eine Liste verwenden, müssen Sie keine Liste zurückgeben. Wenn Sie zurückkehren, wie Microsoft eine Sammlung vorschlägt, verbergen Sie die Tatsache, dass Sie eine Liste verwenden, vor den Verbrauchern Ihrer Klasse und isolieren sie gegen eine interne Änderung.

Harald Scheirich
quelle
1

Etwas hinzuzufügen, obwohl es lange her ist, dass dies gefragt wurde.

Wenn Ihr List<T>Listentyp von abgeleitet ist Collection<T>, können Sie die geschützten virtuellen Methoden, die Collection<T>implementiert werden, nicht implementieren. Dies bedeutet, dass der von Ihnen abgeleitete Typ nicht reagieren kann, wenn Änderungen an der Liste vorgenommen werden. Dies liegt daran, List<T>dass Sie wissen, wann Sie Elemente hinzufügen oder entfernen. In der Lage zu sein, auf Benachrichtigungen zu antworten, ist ein Aufwand und List<T>bietet ihn daher nicht an.

In Fällen, in denen externer Code Zugriff auf Ihre Sammlung hat, haben Sie möglicherweise keine Kontrolle darüber, wann ein Element hinzugefügt oder entfernt wird. Auf diese Collection<T>Weise können Sie feststellen, wann Ihre Liste geändert wurde.

NullReference
quelle
0

Ich sehe kein Problem damit, so etwas zurückzugeben

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

Wenn ich eine nicht verbundene Kopie der internen Daten oder ein getrenntes Ergebnis einer Datenabfrage zurückgegeben habe, kann ich sicher zurückkehren, List<TItem>ohne Implementierungsdetails preiszugeben, und die zurückgegebenen Daten auf bequeme Weise verwenden.

Dies hängt jedoch davon ab, welche Art von Verbraucher ich erwarte - wenn es sich um ein Datenraster handelt, das ich lieber zurückgeben möchte IEnumerable<TItem> , wird dies in den meisten Fällen ohnehin die kopierte Liste der Artikel sein :)

Konstantin Isaev
quelle
-1

Nun, die Collection-Klasse ist wirklich nur eine Wrapper-Klasse für andere Sammlungen, um deren Implementierungsdetails und andere Funktionen zu verbergen. Ich denke, dies hat etwas mit der Eigenschaft zu tun, die das Codierungsmuster in objektorientierten Sprachen verbirgt.

Ich denke, Sie sollten sich darüber keine Sorgen machen, aber wenn Sie dem Code-Analyse-Tool wirklich gefallen möchten, gehen Sie einfach wie folgt vor:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Tamas Czinege
quelle
1
Entschuldigung, das war ein Tippfehler. Ich menat Sammlung <MyClass>. Ich freue mich sehr darauf, C # 4 und generische Kovarianz übrigens in die Hände zu bekommen!
Tamas Czinege
Das Einwickeln in a Collection<T>schützt meines Wissens weder sich selbst noch die zugrunde liegende Sammlung.
Nawfal
Dieser Code sollte "dem Code-Analyse-Tool gefallen". Ich glaube nicht, dass @TamasCzinege irgendwo gesagt hat, dass die Verwendung Collection<T>Ihre zugrunde liegende Sammlung sofort schützt.
Chakrit