Gesicherte Mengen

Manchmal stürmt man so schnell in einer Technologie vorran, dass die kleinen netten Details im Rahmen der Bewegungsunschärfe am Rande unerkannt bleiben. Ich habe das gerade erst im Namespace System.Collections mitbekommen und möchte über ein paar nette Kleinigkeiten schreiben.

Eigentlich glaubt man, alles über Arrays und Collections zu wissen. Man weiß, dass es sie gibt und man stellt sie fröhlich in öffentlichen Interfaces bereit. Nicht selten gibt man so Listen von irgendwas an Aufrufer der eigenen Klassen preis, obwohl man eigentlich schon verhindern möchte, dass sie überschrieben oder sonstwie verändert werden. Viele nutzen dann Schlüsselworte, wie readonly etc. um einem Missbrauch vorzubeugen. Dabei gibt es viel bessere Möglichkeiten.

ArraySegment<T>

Eine Sache, die ich noch nie benutzt aber gefühlt schon oft gebraucht habe, ist das ArraySegment. Seine Benutzung ist relativ schnell beschrieben. Man kann mit ihm ein Teilsegment eines eindimensionalen, nullbasierten Arrays herausgeben. Mehrere Segmente dürfen sich dabei auf einem Original-Array überlappen und man arbeitet beim Durchlaufen nicht auf diesem. Hier ein kleines Beispiel:

var myArray = new int[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
var myFirstSegment = new ArraySegment<int>(myArray, 2, 2);
var mySecondSegment = new ArraySegment<int>(myArray, 1, 6);
Console.WriteLine("Segment 1:");
foreach (var val in myFirstSegment)
{
    Console.WriteLine(val);
}
Console.WriteLine("Segment 2:");
foreach (var val in mySecondSegment)
{
    Console.WriteLine(val);
}
Segment 1:
30
40
Segment 2:
20
30
40
50
60
70

Das kann extrem nützlich sein. In einer Klasse hält man beispielsweise das gesamte Original-Array und gibt Teile davon geschützt nach außen weiter:

class SaveArray
{
        
    private int[] _myArray = new int[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };

    public ArraySegment<int> ItemsForPage(int page, int pageSize)
    {
        if (page * pageSize > _myArray.Length)
        {
            throw new ArgumentOutOfRangeException("Page not reachable.");
        }
        var pageSizeCalculated = pageSize;
        if (((page - 1) * pageSize) + pageSize > _myArray.Length)
        {
            pageSizeCalculated = _myArray.Length - ((page - 1) * pageSize);
        }
        var segment = new ArraySegment<int>(_myArray, ((page - 1) * pageSize), pageSizeCalculated);
        return segment;
    }

}
var myClass = new SaveArray();
foreach (var value in myClass.ItemsForPage(2, 5))
{
    Console.WriteLine(value);
}

Das muss nun nicht bedeuten, dass man Techniken, wie Paging, immer so abbildet. Mit diesem Wissen bewaffnet, kann man aber einige Szenarien wesentlich eleganter lösen, denke ich.

ReadOnly-Container

Ein weiteres schönes Beispiel für nützliche Helfer beim Umgang mit Collections sind die ReadOnly-Modelle für einige Container. Diese gibt es eigentlich schon eine ganze Weile (.NET 2.0). Letztlich handelt es sich um Wrapper um die ganz normalen Collections herum, die ein Verändern von Auflistungen verhindern. Im Namespace System.Collections.ObjectModel und System.Collections.Generic finden sich dazu Klassen und Schnittstellen.

Eine Basis zum Beginnen könnte IReadOnlyCollection sein. Es implementiert IEnumerable und IEnumerable neu und ergänzt es um eine Count-Eigenschaft (ähnlich, wie sein Pendent ICollection). Da es hier, wie gesagt, um Nur-Lese-Collections geht, fehlen Dinge, wie SyncRoot, IsSynchronized oder Copyto().

Die ReadOnlyCollection implementiert nun neben IReadOnlyCollection auch noch IReadOnlyList. Letztlich liefert diese, wie zu erwarten, einen Indexer zum Zugriff auf einzelne Elemente einer Collection.

ReadOnlyCollection ist nun eine Klasse, die die beiden bereits erwähnten Interfaces implementiert und somit scheinbar so agiert, wie eine normale Collection. Das .NET Framework 4.5 liefert hier noch das ReadOnlyDictionary nach, das es vorher nicht gab.

Ein kleines Anwendungsbeispiel soll den Einsatz veranschaulichen:

var stringList = new List<string>() { "hello", "world" };
var roList = stringList.AsReadOnly();
Console.WriteLine(roList[1]);

Wichtig ist der Aufruf von AsReadOnly(), da man nicht einfach eine neue Instanz einer ReadOnlyCollection anlegen kann (aus verständlichen Gründen). Die Ausgabe lautet hier einfach „world“. So gesehen, nichts Spannendes. Versucht man nun aber

roList[1] = "you";
roList.Add("hhh");

funktioniert das nicht, weil bereits der Compiler meckert. Sehr gut. Was aber passiert, wenn man die Ursprungs-Liste stringList verändert?

var stringList = new List<string>() { "hello", "world" };
var roList = stringList.AsReadOnly();
Console.WriteLine(roList[1]);
stringList[1] = "you";
Console.WriteLine(roList[1]);            

Die Ausgabe zeigt nun „you“ anstelle von „world“! Mit anderen Worten: ReadOnlyCollection erzeugt keine InMemory-Copy des Ursprungselementes, sondern verweist intern darauf, sodass Änderungen durchgreifen können.

Und was soll das Ganze bringen? Ein wichtiger Einsatzzweck ist das parallele Programmieren. Ist eine Liste erstmal gefüllt, kann man irgendwelche Worker-Threads mit ReadOnly’s versorgen und ist sicher, dass keine thread-übergreifenden Probleme entstehen können, weil keiner der Worker Änderungen vornehmen kann. Man muss natürlich weiterhin aufpassen, dass man im Ursprung keine Dummheiten macht und damit die Threads in Nirvana schickt, aber immerhin!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.