Was ist die Verwendung der ArraySegment <T> -Klasse?

95

Ich bin gerade auf den ArraySegment<byte>Typ gestoßen, als ich die MessageEncoderKlasse unterklassifiziert habe .

Ich verstehe jetzt, dass es ein Segment eines bestimmten Arrays ist, einen Offset nimmt, nicht aufzählbar ist und keinen Indexer hat, aber ich verstehe seine Verwendung immer noch nicht. Kann jemand bitte mit einem Beispiel erklären?

stackoverflowuser
quelle
8
Es sieht so aus, als wäre ArraySegmentes in .Net 4.5 aufzählbar.
Svick
Für Versuch wie diese Frage ..
Ken Kin

Antworten:

54

ArraySegment<T>ist in .NET 4.5 + und .NET Core viel nützlicher geworden, da es jetzt Folgendes implementiert:

  • IList<T>
  • ICollection<T>
  • IEnumerable<T>
  • IEnumerable
  • IReadOnlyList<T>
  • IReadOnlyCollection<T>

im Gegensatz zur .NET 4-Version, die keinerlei Schnittstellen implementiert hat.

Die Klasse kann jetzt an der wundervollen Welt von LINQ teilnehmen, sodass wir die üblichen LINQ-Aufgaben ausführen können, z. B. den Inhalt abfragen, den Inhalt umkehren, ohne das ursprüngliche Array zu beeinflussen, das erste Element abrufen usw.

var array = new byte[] { 5, 8, 9, 20, 70, 44, 2, 4 };
array.Dump();
var segment = new ArraySegment<byte>(array, 2, 3);
segment.Dump(); // output: 9, 20, 70
segment.Reverse().Dump(); // output 70, 20, 9
segment.Any(s => s == 99).Dump(); // output false
segment.First().Dump(); // output 9
array.Dump(); // no change
Stephen Kennedy
quelle
4
Obwohl sie unerklärlicherweise GetEnumeratorprivat gemacht wurden, bedeutet dies, dass Sie gezwungen sind, IEnumerable<T>(eine Box-Konvertierung) zu verwenden, um es zu nennen. Pfui!
BlueRaja - Danny Pflughoeft
26
  1. Pufferpartitionierung für E / A-Klassen - Verwenden Sie denselben Puffer für gleichzeitige Lese- und Schreibvorgänge und haben Sie eine einzige Struktur, die Sie weitergeben können, um Ihre gesamte Operation zu beschreiben.
  2. Funktionen festlegen - Mathematisch gesehen können Sie mit dieser neuen Struktur alle zusammenhängenden Teilmengen darstellen. Das bedeutet im Grunde, dass Sie Partitionen des Arrays erstellen können, aber Sie können nicht alle Gewinnchancen und alle Ereignisse darstellen. Beachten Sie, dass der von The1 vorgeschlagene Telefon-Teaser mithilfe der ArraySegment-Partitionierung und einer Baumstruktur elegant gelöst werden konnte. Die endgültigen Zahlen könnten ausgeschrieben worden sein, indem zuerst die Baumtiefe durchlaufen wurde. Ich glaube, dies wäre ein ideales Szenario in Bezug auf Speicher und Geschwindigkeit gewesen.
  3. Multithreading - Sie können jetzt mehrere Threads erzeugen, um über dieselbe Datenquelle zu arbeiten, während Sie segmentierte Arrays als Steuergatter verwenden. Schleifen, die diskrete Berechnungen verwenden, können jetzt ganz einfach ausgelagert werden, was die neuesten C ++ - Compiler als Codeoptimierungsschritt beginnen.
  4. UI-Segmentierung - Beschränken Sie Ihre UI-Anzeigen mithilfe segmentierter Strukturen. Sie können jetzt Strukturen speichern, die Datenseiten darstellen, die schnell auf die Anzeigefunktionen angewendet werden können. Einzelne zusammenhängende Arrays können verwendet werden, um diskrete Ansichten oder sogar hierarchische Strukturen wie die Knoten in einer TreeView anzuzeigen, indem ein linearer Datenspeicher in Knotensammlungssegmente segmentiert wird.

In diesem Beispiel sehen wir uns an, wie Sie das ursprüngliche Array, die Offset- und Count-Eigenschaften verwenden und wie Sie die im ArraySegment angegebenen Elemente durchlaufen können.

using System;

class Program
{
    static void Main()
    {
        // Create an ArraySegment from this array.
        int[] array = { 10, 20, 30 };
        ArraySegment<int> segment = new ArraySegment<int>(array, 1, 2);

        // Write the array.
        Console.WriteLine("-- Array --");
        int[] original = segment.Array;
        foreach (int value in original)
        {
            Console.WriteLine(value);
        }

        // Write the offset.
        Console.WriteLine("-- Offset --");
        Console.WriteLine(segment.Offset);

        // Write the count.
        Console.WriteLine("-- Count --");
        Console.WriteLine(segment.Count);

        // Write the elements in the range specified in the ArraySegment.
        Console.WriteLine("-- Range --");
        for (int i = segment.Offset; i < segment.Count+segment.Offset; i++)
        {
            Console.WriteLine(segment.Array[i]);
        }
    }
}

ArraySegment-Struktur - was dachten sie?

Greg McNulty
quelle
3
ArraySegment ist nur eine Struktur. Ich vermute, dass der Zweck darin besteht, die Weitergabe eines Segments eines Arrays zu ermöglichen, ohne eine Kopie davon erstellen zu müssen.
Brian
1
Ich glaube, die Bedingungsanweisung der for-Schleife sollte sein i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 für die Fakten, die Sie erwähnt haben, aber @Eren ist richtig: Sie können die Elemente eines Segments nicht so iterieren.
Şafak Gür
3
Es ist normalerweise angebracht, eine Zuordnung vorzunehmen, wenn Sie einen anderen Code verwenden. Es sind nur gute Manieren. Ihr Beispiel stammt von dotnetperls.com/arraysegment .
1
Es sei denn natürlich, sie haben es aus Ihrer Antwort entlehnt. In diesem Fall sollten sie Ihnen Creds geben. :)
24

Es ist eine mickrige kleine Soldatenstruktur, die nichts anderes tut, als einen Verweis auf ein Array zu behalten und einen Indexbereich zu speichern. Ein wenig gefährlich, seien Sie vorsichtig, dass es keine Kopie der Array-Daten erstellt und das Array in keiner Weise unveränderlich macht oder die Notwendigkeit der Unveränderlichkeit zum Ausdruck bringt. Das typischere Programmiermuster besteht darin, das Array und eine Längenvariable oder einen Längenparameter einfach beizubehalten oder zu übergeben, wie dies bei den .NET BeginRead () -Methoden, String.SubString (), Encoding.GetString () usw. usw. der Fall ist.

In .NET Framework wird es nur wenig verwendet, außer bei einem bestimmten Microsoft-Programmierer, der an Web-Sockets gearbeitet hat und WCF gefällt. Welches ist wahrscheinlich die richtige Anleitung, wenn Sie es mögen, dann verwenden Sie es. In .NET 4.6 wurde ein Peek-a-Boo durchgeführt, das von der hinzugefügten MemoryStream.TryGetBuffer () -Methode verwendet wird. outIch nehme an , zwei Argumente vorzuziehen.

Im Allgemeinen steht der universellere Begriff der Slices ganz oben auf der Wunschliste der wichtigsten .NET-Ingenieure wie Mads Torgersen und Stephen Toub. Letztere array[:]haben vor einiger Zeit den Syntaxvorschlag gestartet. Sie können auf dieser Roslyn-Seite sehen, worüber sie nachgedacht haben . Ich würde davon ausgehen, dass es letztendlich darauf ankommt, CLR-Unterstützung zu erhalten. Dies wird für C # Version 7 afaik aktiv in Betracht gezogen. Behalten Sie System.Slices im Auge . .

Update: Dead Link, dieser wurde in Version 7.2 als Span ausgeliefert .

Update2: Mehr Unterstützung in C # Version 8.0 mit Range- und Index-Typen und einer Slice () -Methode.

Hans Passant
quelle
"Es ist nicht sehr nützlich" - ich fand es unglaublich nützlich in einem System, das leider aufgrund von Speicherbeschränkungen
Mikrooptimierungen
5
Okay, okay, ich brauche nicht wirklich ein Zeugnis von allen, die es sich angewöhnt haben, es zu benutzen :) Am besten, Sie stimmen dem Kommentar von @ CRice zu. Wie gesagt, "wenn es dir gefällt, dann benutze es". Also benutze es. Scheiben werden fantastisch sein, kann es kaum erwarten.
Hans Passant
Es gibt einen ReadOnlySpan für diese unveränderlichen Puristen da draußen.
Arek Bal
7

Was ist mit einer Wrapper-Klasse? Nur um zu vermeiden, dass Daten in temporäre Puffer kopiert werden.

public class SubArray<T> {
        private ArraySegment<T> segment;

        public SubArray(T[] array, int offset, int count) {
            segment = new ArraySegment<T>(array, offset, count);
        }
        public int Count {
            get { return segment.Count; }
        }

        public T this[int index] {
            get {
               return segment.Array[segment.Offset + index];
            }
        }

        public T[] ToArray() {
            T[] temp = new T[segment.Count];
            Array.Copy(segment.Array, segment.Offset, temp, 0, segment.Count);
            return temp;
        }

        public IEnumerator<T> GetEnumerator() {
            for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) {
                yield return segment.Array[i];
            }
        }
    } //end of the class

Beispiel:

byte[] pp = new byte[] { 1, 2, 3, 4 };
SubArray<byte> sa = new SubArray<byte>(pp, 2, 2);

Console.WriteLine(sa[0]);
Console.WriteLine(sa[1]);
//Console.WriteLine(b[2]); exception

Console.WriteLine();
foreach (byte b in sa) {
    Console.WriteLine(b);
}

Ausgabe:

3
4

3
4
Nergeia
quelle
Sehr nützlicher Kumpel, danke, beachten Sie, dass Sie es implementieren lassen und IEnumerable<T>dann IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN
5

Das ArraySegment ist VIEL nützlicher als Sie vielleicht denken. Führen Sie den folgenden Komponententest durch und lassen Sie sich überraschen!

    [TestMethod]
    public void ArraySegmentMagic()
    {
        var arr = new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

        var arrSegs = new ArraySegment<int>[3];
        arrSegs[0] = new ArraySegment<int>(arr, 0, 3);
        arrSegs[1] = new ArraySegment<int>(arr, 3, 3);
        arrSegs[2] = new ArraySegment<int>(arr, 6, 3);
        for (var i = 0; i < 3; i++)
        {
            var seg = arrSegs[i] as IList<int>;
            Console.Write(seg.GetType().Name.Substring(0, 12) + i);
            Console.Write(" {");
            for (var j = 0; j < seg.Count; j++)
            {
                Console.Write("{0},", seg[j]);
            }
            Console.WriteLine("}");
        }
    }

Sie sehen, alles, was Sie tun müssen, ist ein ArraySegment in IList umzuwandeln, und es wird all die Dinge tun, die Sie wahrscheinlich erwartet haben. Beachten Sie, dass der Typ immer noch ArraySegment ist, obwohl er sich wie eine normale Liste verhält.

AUSGABE:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Ben Stabile
quelle
4
Es ist schade, dass es notwendig ist, es zu besetzen IList<T>. Ich würde erwarten, dass der Indexer ist public.
Xmedeko
2
Für alle, die auf diese Antwort stoßen und sie für eine Wunderlösung halten, empfehle ich, zunächst Ihre Leistungsanforderungen zu berücksichtigen und diese im Vergleich zum direkten Zugriff auf das ursprüngliche Array unter Verwendung der Indexbeschränkungen aus dem Arraysegment zu vergleichen. Das Casting in eine IList erfordert nachfolgende Methodenaufrufe (einschließlich des Indexers), um durch die IList-Schnittstelle zu springen, bevor die Implementierung erreicht wird. Im Internet gibt es viele Diskussionen, in denen über die Leistungskosten der Verwendung abstrahierter Anrufe in engen Schleifen gesprochen wird. Lesen Sie hier: github.com/dotnet/coreclr/issues/9105
JamesHoux
3

Mit einfachen Worten: Es wird auf ein Array verwiesen, sodass Sie mehrere Verweise auf eine einzelne Array-Variable haben können, von denen jede einen anderen Bereich hat.

Tatsächlich hilft es Ihnen, Abschnitte eines Arrays strukturierter zu verwenden und zu übergeben, anstatt mehrere Variablen zu haben, um Startindex und Länge zu halten. Außerdem bietet es Sammlungsschnittstellen, mit denen Sie einfacher mit Array-Abschnitten arbeiten können.

Zum Beispiel machen die folgenden zwei Codebeispiele dasselbe, eines mit ArraySegment und eines ohne:

        byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
        ArraySegment<byte> seg1 = new ArraySegment<byte>(arr1, 2, 2);
        MessageBox.Show((seg1 as IList<byte>)[0].ToString());

und,

        byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
        int offset = 2;
        int length = 2;
        byte[] arr2 = arr1;
        MessageBox.Show(arr2[offset + 0].ToString());

Offensichtlich ist das erste Code-Snippet bevorzugter, insbesondere wenn Sie Array-Segmente an eine Funktion übergeben möchten.

M. Mahdipour
quelle