2009-08-02 10 views
3

Ich habe Methoden, die private Auflistungen an den Aufrufer zurückgeben, und ich möchte verhindern, dass der Aufrufer die zurückgegebenen Auflistungen ändert.Wie kann verhindert werden, dass ein Methodenaufrufer eine zurückgegebene Sammlung ändert?

private readonly Foo[] foos; 

public IEnumerable<Foo> GetFoos() 
{ 
    return this.foos; 
} 

Im Moment ist die Privatsammlung ist eine feste Anordnung, aber in der Zukunft die Sammlung könnte eine Liste werden, wenn die Notwendigkeit für das Hinzufügen neue Elemente zur Laufzeit entsteht.

Es gibt mehrere Lösungen, die verhindern, dass der Aufrufer die Sammlung ändert. Die Rückkehr IEnumerable<T> ist die einfachste Lösung, aber der Aufrufer kann immer noch den Rückgabewert auf IList<T> herauf-Cast und die Auflistung ändern.

((IList<Foo>)GetFoos())[0] = otherFoo; 

die Sammlungen Klonen hat den offensichtlichen Nachteil, dass es zwei Sammlungen, die unabhängig voneinander entwickeln können. Bisher habe ich die folgenden Optionen in Betracht gezogen.

  1. Verpackung der Sammlung in ReadOnlyCollection<T>.
  2. Zurückgeben eines der von der Klasse Enumerable definierten LINQ-Iteratoren durch Ausführen einer Dummy-Projektion wie list.Select(item => item). Eigentlich denke ich über Where(item => true), weil der zurückgegebene Iterator leichter scheint.
  3. Schreiben eines benutzerdefinierten Wrappers.

Was Ich mag nicht über ReadOnlyCollection<T> ist, dass es implementiert IList<T> und ruft Add() oder den Zugriff auf die Indexer Ausnahmen verursachen. Während dies in der Theorie absolut korrekt ist, prüft fast kein echter Code IList<T>.IsReadOnly oder IList<T>.IsFixedSize.

Verwendung der LINQ-Iteratoren - Ich habe den Code in eine Erweiterungsmethode MakeReadOnly() eingewickelt - verhindert dieses Szenario, aber es hat den Geschmack eines Hacks.

Schreiben eines benutzerdefinierten Wrappers? Das Rad neu erfinden?

Irgendwelche Gedanken, Überlegungen oder andere Lösungen?


Während diese Frage Tagging, entdeckte ich this Stack Overflow question ich vor nicht bemerkt. Jon Skeet schlägt vor, den "LINQ-Hack" auch zu verwenden, aber noch effizienter unter Verwendung von Skip(0).

+1

Wenn es keine Sicherheitsprobleme (potentiell feindselige Anrufer) gibt, mache ich mir im Allgemeinen keine Gedanken über den Schutz vor unklugem Casting oder den Aufruf von Add in einer ReadOnlyCollection. Törichte Entwickler können Reflexionen verwenden und auch auf Interna zugreifen. Es ist schwierig, Code idiotensicher zu machen. Ziel ist es, vor unschuldiger Nutzung der öffentlichen Schnittstelle zu schützen. – TrueWill

Antwort

5

Leider gibt es keine Möglichkeit genau zu erreichen, was Sie in der aktuellen Version des Frameworks suchen. Es hat einfach kein Konzept einer indexierbaren unveränderlichen/schreibgeschützten Sammlung sowohl für den konkreten Typ als auch für den Schnittstellenstil.

Wie Sie hingewiesen haben, funktioniert ReadOnlyCollection<T> OK auf der Betontyp Seite. Es gibt jedoch keine entsprechende Schnittstelle, die auch statisch nur lesbar ist.

Du bist nur eine echte Wahl ist ...

  • Ihre eigene Sammlung Klasse definieren
  • Entweder nur IEnumerable<T> implementieren oder eine Notwendigkeit schreibgeschützte Schnittstelle, die Ihre Sammlung Geräte definieren.
+0

Ich akzeptierte diese Antwort, weil es klarstellt, dass es keine integrierte Unterstützung gibt. A entschied sich, bei Enumerable.Skip (0) zu bleiben. –

+0

@ DanielBrückner: Welchen wirklichen Vorteil hat das gegenüber dem Einbinden in eine 'ReadOnlyCollection 'und Casting nach' IEnumerable '? Die Tatsache, dass das resultierende Objekt in "IList " umgewandelt werden kann, wird die Performance einiger Linq-Methoden wie "Count" und "Last" enorm verbessern. – supercat

0

Wie wäre es mit einer tiefen Kopie des zurückgegebenen Objekts? [So wird es keine Auswirkungen auf die ursprüngliche Sammlung sein, auch wenn der Anrufer Änderungen an der Kopie zu machen entscheidet zurückgegeben]

+0

Eine Kopie der Sammlung ist nicht wünschenswert, da der Aufrufer nur einen Schnappschuss erhalten würde und Änderungen an der Sammlung nicht bemerken würde. –

0

Liste und Array haben eine AsReadOnly Methode:

public IEnumerable<Foo> GetFoos() 
{ 
    return Array.AsReadOnly(this.foos); 
    // or if it is a List<T> 
    // return this.foos.AsReadOnly(); 
} 
+0

Diese Methode gibt die Sammlung zurück, die in einer ReadOnlyCollection verpackt ist, und ich mag es nicht, dass diese Klasse IList wie in der Frage beschrieben implementiert. –

0

In solchen Fällen i ein Verfahren erstellen Die Klasse kann auf einzelne Elemente der privaten Sammlung zugreifen. Sie können auch Indexer (http://msdn.microsoft.com/en-us/library/6x16t2tx.aspx) für die Klasse, die private Sammlung enthält, realisieren.

+0

Das Implementieren einer Methode zum Erhalten eines einzelnen Elements hat mindestens zwei Nachteile - eine zweite Methode oder eine Eigenschaft zum Zurückgeben der Anzahl der Elemente in der Auflistung ist erforderlich, und alle Vorteile von IEnumerable - foreach und LINQ - gehen verloren. Das Erstellen eines Indexers erfordert eine Methode oder Eigenschaft, die ebenfalls die Anzahl der Elemente zurückgibt, und bei einigen Klassen gibt es mehrere Methoden, die Sammlungen zurückgeben. Dies schaltet die Indexer aus. –

+0

Ja, sicher, es hängt von Ihren Aufgaben ab. Sie können jedoch ein Feld hinzufügen, das die Anzahl der Elemente in der privaten Sammlung zurückgibt. Sie können auch innere Klasse erstellen, die die Zugriffslogik realisiert. – RollingStone