¿Cuál es el uso de la clase ArraySegment <T>?

97

Me encontré con el ArraySegment<byte>tipo mientras subclasificaba la MessageEncoderclase.

Ahora entiendo que es un segmento de una matriz dada, toma un desplazamiento, no es enumerable y no tiene un indexador, pero todavía no entiendo su uso. ¿Alguien puede explicar con un ejemplo?

stackoverflowuser
fuente
8
Parece ArraySegmentenumerable en .Net 4.5.
svick
Para un intento como esta pregunta ..
Ken Kin

Respuestas:

55

ArraySegment<T>se ha vuelto mucho más útil en .NET 4.5 + y .NET Core, ya que ahora implementa:

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

a diferencia de la versión .NET 4 que no implementó ninguna interfaz.

La clase ahora puede participar en el maravilloso mundo de LINQ, por lo que podemos hacer las cosas habituales de LINQ como consultar el contenido, revertir el contenido sin afectar la matriz original, obtener el primer elemento, y así sucesivamente:

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
fuente
4
Aunque inexplicablemente se hicieron GetEnumeratorprivados, lo que significa que estás obligado a lanzar a IEnumerable<T>(una conversión de boxeo) para llamarlo. ¡Uf!
BlueRaja - Danny Pflughoeft
27
  1. Partición de búfer para clases de E / S: use el mismo búfer para operaciones de lectura y escritura simultáneas y tenga una estructura única que pueda pasar y describa toda su operación.
  2. Establecer funciones: matemáticamente hablando, puede representar cualquier subconjunto contiguo utilizando esta nueva estructura. Eso básicamente significa que puede crear particiones de la matriz, pero no puede representar todas las probabilidades y todos los pares. Tenga en cuenta que el adelanto telefónico propuesto por The1 podría haberse resuelto elegantemente utilizando la partición ArraySegment y una estructura de árbol. Los números finales podrían haberse escrito atravesando primero la profundidad del árbol. Este habría sido un escenario ideal en términos de memoria y velocidad, creo.
  3. Subprocesos múltiples: ahora puede generar varios subprocesos para operar sobre la misma fuente de datos mientras usa matrices segmentadas como puerta de control. Los bucles que utilizan cálculos discretos ahora se pueden organizar con bastante facilidad, algo que los últimos compiladores de C ++ están comenzando a hacer como un paso de optimización del código.
  4. Segmentación de la interfaz de usuario: restrinja las pantallas de la interfaz de usuario mediante estructuras segmentadas. Ahora puede almacenar estructuras que representan páginas de datos que se pueden aplicar rápidamente a las funciones de visualización. Se pueden usar matrices contiguas individuales para mostrar vistas discretas, o incluso estructuras jerárquicas como los nodos en un TreeView al segmentar un almacén de datos lineal en segmentos de colección de nodos.

En este ejemplo, veremos cómo puede usar la matriz original, las propiedades Offset y Count, y también cómo puede recorrer los elementos especificados en 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]);
        }
    }
}

Estructura de segmento de matriz: ¿en qué estaban pensando?

Greg McNulty
fuente
3
ArraySegment es solo una estructura. Mi mejor suposición es que su propósito es permitir que un segmento de una matriz se pase sin tener que hacer una copia de él.
Brian
1
Creo que la declaración de condición del ciclo for debería ser i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 por los hechos que mencionaste, pero @Eren tiene razón: no puedes iterar los elementos de un segmento como ese.
Şafak Gür
3
Por lo general, es apropiado dar atribución cuando usa el código de otra persona. Son buenos modales. Su ejemplo se origina en dotnetperls.com/arraysegment .
1
A menos que, por supuesto, lo hayan tomado prestado de su respuesta. En cuyo caso, deberían darle crédito. :)
26

Es una estructura de pequeño soldado insignificante que no hace más que mantener una referencia a una matriz y almacena un rango de índice. Un poco peligroso, tenga en cuenta que no hace una copia de los datos de la matriz y de ninguna manera hace que la matriz sea inmutable o exprese la necesidad de inmutabilidad. El patrón de programación más típico es simplemente mantener o pasar la matriz y una variable o parámetro de longitud, como se hace en los métodos .NET BeginRead (), String.SubString (), Encoding.GetString (), etc., etc.

No se usa mucho dentro de .NET Framework, excepto por lo que parece ser un programador de Microsoft en particular que trabajó en sockets web y le gustó a WCF. Que es probablemente la guía adecuada, si te gusta, úsala. Hizo un peek-a-boo en .NET 4.6, el método MemoryStream.TryGetBuffer () agregado lo usa. Preferiría tener dos outargumentos, supongo.

En general, la noción más universal de sectores ocupa un lugar destacado en la lista de deseos de los principales ingenieros de .NET como Mads Torgersen y Stephen Toub. Este último inició la array[:]propuesta de sintaxis hace un tiempo, puedes ver lo que han estado pensando en esta página de Roslyn . Asumiría que obtener soporte CLR es de lo que depende en última instancia. Esto se está considerando activamente para la versión 7 de C # afaik, manténgase atento a System.Slices .

Actualización: enlace muerto, esto se envió en la versión 7.2 como Span .

Update2: más compatibilidad en la versión 8.0 de C # con los tipos Range e Index y un método Slice ().

Hans Passant
fuente
"No es muy útil '. Lo encontré increíblemente útil en un sistema que desafortunadamente requería micro optimizaciones debido a la limitación de la memoria. El hecho de que también haya otras soluciones" típicas "no resta valor a su utilidad
AaronHS
5
De acuerdo, de acuerdo, realmente no necesito un testimonio de todos los que tienen el hábito de usarlo :) Lo mejor es votar a favor del comentario de @ CRice. Como se señaló, "si te gusta, úsalo". Así que úsalo. Las rebanadas serán increíbles, no puedo esperar.
Hans Passant
Hay un ReadOnlySpan para esos puristas inmutables.
Arek Bal
7

¿Qué pasa con una clase contenedora? Solo para evitar copiar datos a búferes temporales.

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

Ejemplo:

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

Salida:

3
4

3
4
nergeia
fuente
Compañero muy útil, gracias, nota que puede hacer que sea poner en práctica IEnumerable<T>a continuación, añadir IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
Maya
5

ArraySegment es MUCHO más útil de lo que piensas. ¡Intente ejecutar la siguiente prueba unitaria y prepárese para sorprenderse!

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

Verá, todo lo que tiene que hacer es lanzar un ArraySegment a IList y hará todas las cosas que probablemente esperaba que hiciera en primer lugar. Observe que el tipo sigue siendo ArraySegment, aunque se comporta como una lista normal.

SALIDA:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Ben Stabile
fuente
4
Es una pena que sea necesario lanzarlo IList<T>. Esperaría que el indexador lo fuera public.
xmedeko
2
Para cualquiera que encuentre esta respuesta y piense que es una solución milagrosa, le recomiendo primero considerar sus necesidades de rendimiento y compararlo con el acceso directo a la matriz original utilizando las restricciones de índice del segmento de matriz. La conversión a IList requiere llamadas de método posteriores (incluido el indexador) para pasar por la interfaz IList antes de llegar a la implementación. Hay muchas discusiones en Internet donde la gente habla sobre el costo de rendimiento de usar llamadas abstractas en bucles estrechos. Lea aquí: github.com/dotnet/coreclr/issues/9105
JamesHoux
3

En palabras simples: mantiene la referencia a una matriz, lo que le permite tener múltiples referencias a una única variable de matriz, cada una con un rango diferente.

De hecho, le ayuda a usar y pasar secciones de una matriz de una manera más estructurada, en lugar de tener múltiples variables, para mantener el índice de inicio y la longitud. También proporciona interfaces de colección para trabajar más fácilmente con secciones de matriz.

Por ejemplo, los siguientes dos ejemplos de código hacen lo mismo, uno con ArraySegment y otro sin:

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

y,

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

Obviamente, se prefiere más el primer fragmento de código, especialmente cuando desea pasar segmentos de matriz a una función.

M.Mahdipour
fuente