Qual è l'uso della classe ArraySegment <T>?


97

Mi sono appena imbattuto nel ArraySegment<byte>tipo durante la sottoclasse della MessageEncoderclasse.

Ora capisco che è un segmento di un dato array, prende un offset, non è enumerabile e non ha un indicizzatore, ma ancora non riesco a capire il suo utilizzo. Qualcuno può spiegare con un esempio?


8
Sembra che ArraySegmentsia enumerabile in .Net 4.5.
svick

Per un tentativo come questa domanda ..
Ken Kin

Risposte:


55

ArraySegment<T>è diventato molto più utile in .NET 4.5 + e .NET Core poiché ora implementa:

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

al contrario della versione .NET 4 che non implementava alcuna interfaccia.

La classe è ora in grado di prendere parte al meraviglioso mondo di LINQ, quindi possiamo fare le solite cose LINQ come interrogare i contenuti, invertire i contenuti senza influenzare l'array originale, ottenere il primo elemento e così via:

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

4
Sebbene siano inspiegabilmente resi GetEnumeratorprivati, il che significa che sei costretto a lanciare IEnumerable<T>(una conversione di boxe) per chiamarlo. Uffa!
BlueRaja - Danny Pflughoeft

27
  1. Suddivisione del buffer per le classi di I / O - Utilizza lo stesso buffer per operazioni di lettura e scrittura simultanee e disponi di un'unica struttura che puoi passare per descrivere l'intera operazione.
  2. Set Functions - Matematicamente parlando puoi rappresentare qualsiasi sottoinsieme contiguo usando questa nuova struttura. Ciò significa fondamentalmente che puoi creare partizioni dell'array, ma non puoi rappresentare dire tutte le probabilità e tutti i pari. Si noti che il teaser telefonico proposto da The1 avrebbe potuto essere risolto elegantemente utilizzando il partizionamento ArraySegment e una struttura ad albero. I numeri finali avrebbero potuto essere scritti attraversando prima la profondità dell'albero. Questo sarebbe stato uno scenario ideale in termini di memoria e velocità, credo.
  3. Multithreading - È ora possibile generare più thread per operare sulla stessa origine dati mentre si utilizzano array segmentati come gate di controllo. I cicli che utilizzano calcoli discreti possono ora essere elaborati abbastanza facilmente, cosa che gli ultimi compilatori C ++ stanno iniziando a fare come passaggio di ottimizzazione del codice.
  4. Segmentazione dell'interfaccia utente: vincola la visualizzazione dell'interfaccia utente utilizzando strutture segmentate. È ora possibile memorizzare strutture che rappresentano pagine di dati che possono essere rapidamente applicate alle funzioni di visualizzazione. È possibile utilizzare array contigui singoli per visualizzare viste discrete o anche strutture gerarchiche come i nodi in un TreeView segmentando un archivio dati lineare in segmenti di raccolta di nodi.

In questo esempio, esaminiamo come puoi usare l'array originale, le proprietà Offset e Count e anche come puoi scorrere gli elementi specificati in ArraySegment.

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 Structure: cosa stavano pensando?


3
ArraySegment è solo una struttura. La mia ipotesi migliore è che il suo scopo sia quello di consentire il passaggio di un segmento di un array senza doverne fare una copia.
Brian

1
Credo che la dichiarazione della condizione del ciclo for dovrebbe essere i < segment.Offset + segment.Count.
Eren Ersönmez

1
+1 per i fatti che hai menzionato ma @Eren ha ragione: non puoi iterare gli elementi di un segmento in questo modo.
Şafak Gür

3
Di solito è appropriato dare l'attribuzione quando usi il codice di qualcun altro. Sono solo buone maniere. Il tuo esempio ha origine da dotnetperls.com/arraysegment .

1
A meno che, naturalmente, non l'abbiano preso in prestito dalla tua risposta. In tal caso, dovrebbero darti dei crediti. :)

26

È una piccola struttura di soldatini che non fa altro che mantenere un riferimento a un array e memorizzare un intervallo di indici. Un po 'pericoloso, attenzione che non fa una copia dei dati dell'array e non rende in alcun modo immutabile l'array né esprime la necessità di immutabilità. Il modello di programmazione più tipico consiste nel mantenere o passare semplicemente l'array e una variabile o un parametro di lunghezza, come avviene nei metodi .NET BeginRead (), String.SubString (), Encoding.GetString (), ecc, ecc.

Non è molto utile all'interno di .NET Framework, ad eccezione di quello che sembra un particolare programmatore Microsoft che ha lavorato su socket web e WCF lo ama. Che è probabilmente la guida corretta, se ti piace, usala. Ha fatto un peek-a-boo in .NET 4.6, il metodo aggiunto MemoryStream.TryGetBuffer () lo usa. outPreferisco avere due argomenti presumo.

In generale, la nozione più universale di slice è in cima alla lista dei desideri dei principali ingegneri .NET come Mads Torgersen e Stephen Toub. Quest'ultimo ha dato il via alla array[:]proposta di sintassi qualche tempo fa, puoi vedere a cosa hanno pensato in questa pagina di Roslyn . Presumo che ottenere il supporto CLR sia ciò su cui alla fine dipende. Questo è attivamente pensato per la versione 7 di C # afaik, tieni d'occhio System.Slices .

Aggiornamento: collegamento morto, fornito nella versione 7.2 come Span .

Update2: più supporto in C # versione 8.0 con i tipi Range e Index e un metodo Slice ().


"Non è molto utile" - L'ho trovato incredibilmente utile in un sistema che purtroppo richiedeva micro ottimizzazioni a causa della limitazione della memoria. Il fatto che ci siano anche altre soluzioni "tipiche" non toglie nulla alla sua utilità
AaronHS

5
Ok, ok, non ho davvero bisogno di una testimonianza da tutti coloro che hanno preso l'abitudine di usarlo :) Meglio votare a favore del commento di @ CRice. Come notato, "se ti piace, usalo". Quindi usalo. Le fette saranno fantastiche, non vedo l'ora.
Hans Passant

C'è un ReadOnlySpan per quei puristi immutabili là fuori.
Arek Bal

7

Che ne dici di una classe wrapper? Solo per evitare di copiare i dati nei buffer temporali.

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

Esempio:

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);
}

Uscita:

3
4

3
4

Amico molto utile, grazie, nota che puoi farlo implementare IEnumerable<T>quindi aggiungi IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN

5

ArraySegment è MOLTO più utile di quanto potresti pensare. Prova a eseguire il seguente test unitario e preparati a rimanere sbalordito!

    [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("}");
        }
    }

Vedi, tutto ciò che devi fare è lanciare un ArraySegment a IList e farà tutte le cose che probabilmente ti aspettavi che facesse in primo luogo. Si noti che il tipo è ancora ArraySegment, anche se si comporta come un normale elenco.

PRODUZIONE:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}

4
È un peccato che sia necessario lanciarlo IList<T>. Mi aspetto che sia l'indicizzatore public.
xmedeko

2
Per chiunque trovi questa risposta e pensi che sia una soluzione miracolosa, consiglio prima di considerare le tue esigenze di prestazioni e confrontarle rispetto all'accesso diretto all'array originale utilizzando i vincoli di indice dal segmento dell'array. Il cast a un IList richiede successive chiamate al metodo (incluso l'indicizzatore) per passare attraverso l'interfaccia IList prima di raggiungere l'implementazione. Ci sono molte discussioni su Internet in cui si parla del costo in termini di prestazioni dell'utilizzo di chiamate astratte in cicli ristretti. Leggi qui: github.com/dotnet/coreclr/issues/9105
JamesHoux

3

In parole semplici: mantiene il riferimento a un array, consentendo di avere più riferimenti a una singola variabile di array, ciascuno con un intervallo diverso.

In effetti ti aiuta a usare e passare sezioni di un array in un modo più strutturato, invece di avere più variabili, per contenere l'indice iniziale e la lunghezza. Inoltre fornisce interfacce di raccolta per lavorare più facilmente con le sezioni dell'array.

Ad esempio, i due esempi di codice seguenti fanno la stessa cosa, uno con ArraySegment e uno senza:

        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());

e,

        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());

Ovviamente il primo frammento di codice è più preferito, specialmente quando si desidera passare segmenti di array a una funzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.