ReadOnlyCollection

Jeder, der eine halbwegs umfangreiche Klassen-Sammlung in C# designen muss, hat gute Chancen, auf folgende grundlegende Design-Frage zu stossen: Eine Klasse soll eine List enthalten, die zwar gegen aussen sichtbar ist, die man aber nicht direkt verändern darf – sämtliche Änderungen sollen quasi indirekt mit Hilfe von Methoden der Klasse erfolgen. Für eine solche Anforderung kann es viele denkbare Gründe geben; schon nur, wenn man die Anzahl Änderungen an der List zählen möchte, sollte es besser nicht möglich sein, dass sich jemand an der Zählung vorbeimogelt, indem er sich die Liste geben lässt und sie dann selbst verändert.

Ein kleines synthetisches Codebeispiel hierzu:

namespace ReadOnly {
  class Container {

    private List<int> numbers;
    public List<int> Numbers {
      get { return numbers; }
    }

    public void AddNumber(int n) {
      numbers.Add(n);
    }

    public Container() {
      numbers = new List<int>();
    }
  }

  class Program {
    static void Main(string[] args) {
      Container c=new Container();
      c.AddNumber(5);     // soll nur so funktionieren
      c.Numbers.Add(5);   // soll verboten sein
    }
  }
}

Mein erster Gedanke war, dass es in C# vielleicht ein Äquivalent zu C++ const gibt und man damit etwas machen kann. Eine Abklärung ergab, dass die CLR zwar eine gewisse Unterstützung bietet in dieser Richtung (für Managed C++), diese aber in C# nicht zur Verfügung steht.

Dann stiess ich auf die Property IsReadOnly, welche im Interface IList enthalten ist. Das sieht zwar auf den ersten Blick hoffnungsvoll aus, aber die Anwesenheit dieser Property im Interface heisst noch lange nicht, dass man im Falle einer „templated class“ etwas damit machen kann. Ich habe auf jeden Fall keinen Weg gefunden, im konkreten Falle einer List<int> deren Property IsReadOnly irgendwie zu überdefinieren, und selbst wenn dies gelänge, würden sich wohl Methoden wie Add der Template-basierten Klasse überhaupt nicht dafür interessieren.

Schliesslich förderte meine Suche die Klasse ReadOnlyCollection zu Tage, welche man wie folgt auf das Problem ansetzen kann:

namespace ReadOnly {
  class Container {

    private List<int> numbers;
    public ReadOnlyCollection<int> Numbers {
      get { return numbers.AsReadOnly(); }
    }

    public void AddNumber(int n) {
      numbers.Add(n);
    }

    public Container() {
      numbers = new List<int>();
    }
  }

  class Program {
    static void Main(string[] args) {
      Container c = new Container();
      c.AddNumber(5);     // funktioniert nur so
      c.Numbers.Add(5);   // compiliert nicht, 'ReadOnlyCollection.Add' gibt es nicht
    }
  }
}

Etwas unbefriedigend an dieser Lösung ist, dass mit Hilfe der Methode AsReadOnly ein neues Objekt erzeugt werden muss.

In irgendeinem Forum habe ich dann folgende Lösungs-Variante gefunden, welche ohne ein neues Objekt auskommt:

namespace ReadOnly {
  class Container {

    private List<int> numbers;
    public IEnumerable<int> Numbers {
      get { return numbers; }
    }

    public void AddNumber(int n) {
      numbers.Add(n);
    }

    public Container() {
      numbers = new List<int>();
    }
  }

  class Program {
    static void Main(string[] args) {
      Container c = new Container();
      c.AddNumber(5);     // funktioniert nur so
      c.Numbers.Add(5);   // compiliert nicht, 'IEnumerable.Add' gibt es nicht
    }
  }
}

Eine List<int> implementiert natürlich selbst bereits das Interface IEnumerable, so dass die Sache rein zur Compilierzeit stattfindet und die Property Number technisch gesehen gleich die Liste selbst zurückgibt. Unschön ist allerdings, dass man mit einem IEnumerable-Objekt nichts weiter machen kann als per foreach die Elemente durchgehen; noch nicht einmal die Anzahl Elemente kann man direkt abfragen.

Nach einigem Abwägen habe ich beschlossen, für die Megos-eigene Klassen-Bibliothek Triton, die gerade entsteht, das Problem zu ignorieren und keine technische Vorkehrungen zu treffen, um mit Hilfe einer der geschilderten Methoden Veränderungen an solchen Listen zu verhindern. Wir sind wenige, erfahrene, disziplinierte Programmierer, die keine Mühe haben mit dem Befolgen von Konventionen wie „Verändere keine Listen-Properties direkt“.

Natürlich sähe das anders aus, wenn man z.B. eine kommerziell vertriebene Klassen-Bibliothek für den Gebrauch durch viele andere Programmierer bauen müsste. Aber auch da gäbe es vielleicht die Möglichkeit, die direkten Veränderungen selbst nicht zu verunmöglichen, sie aber dennoch zu entdecken und mit einer Exception zu beantworten, indem man einerseits die Anzahl Aufrufe von AddNumber zählt und andererseits an „strategischen“ Orten im Code die Anzahl Elemente der Liste gegen diesen Zähler prüft.

Veröffentlicht in Keine Kategorie. Schlagwörter: , . Leave a Comment »

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: