Qual é o uso da classe ArraySegment <T>?

97

Acabei de encontrar o ArraySegment<byte>tipo enquanto fazia a subclasse da MessageEncoderclasse.

Agora entendo que é um segmento de uma determinada matriz, tem um deslocamento, não é enumerável e não tem um indexador, mas ainda não consigo entender seu uso. Alguém pode explicar com um exemplo?

stackoverflowuser
fonte
8
Parece que ArraySegmenté enumerável em .Net 4.5.
svick
Para uma tentativa como esta pergunta ..
Ken Kin

Respostas:

55

ArraySegment<T>tornou-se muito mais útil no .NET 4.5 + e .NET Core , pois agora implementa:

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

ao contrário da versão .NET 4, que não implementou nenhuma interface.

A classe agora é capaz de participar do maravilhoso mundo do LINQ para que possamos fazer as coisas normais do LINQ, como consultar o conteúdo, reverter o conteúdo sem afetar a matriz original, obter o primeiro item e assim por diante:

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
fonte
4
Embora eles inexplicavelmente tornassem GetEnumeratorprivado, significando que você é forçado a lançar para IEnumerable<T>(uma conversão de boxe) para convocar. Ugh!
BlueRaja - Danny Pflughoeft
27
  1. Partição de buffer para classes de E / S - Use o mesmo buffer para operações simultâneas de leitura e gravação e tenha uma única estrutura que você pode transmitir para descrever toda a sua operação.
  2. Set Functions - Matematicamente falando, você pode representar qualquer subconjunto contíguo usando esta nova estrutura. Isso basicamente significa que você pode criar partições do array, mas não pode representar, digamos, todas as probabilidades e pares. Observe que o teaser de telefone proposto por The1 poderia ter sido elegantemente resolvido usando o particionamento ArraySegment e uma estrutura de árvore. Os números finais podem ter sido escritos atravessando primeiro a profundidade da árvore. Este teria sido um cenário ideal em termos de memória e velocidade, acredito.
  3. Multithreading - agora você pode gerar vários threads para operar na mesma fonte de dados enquanto usa matrizes segmentadas como porta de controle. Loops que usam cálculos discretos agora podem ser distribuídos com bastante facilidade, algo que os compiladores C ++ mais recentes estão começando a fazer como uma etapa de otimização de código.
  4. Segmentação da IU - restrinja suas exibições da IU usando estruturas segmentadas. Agora você pode armazenar estruturas que representam páginas de dados que podem ser aplicadas rapidamente às funções de exibição. Arrays contíguos únicos podem ser usados ​​para exibir visualizações discretas, ou mesmo estruturas hierárquicas, como os nós em um TreeView, segmentando um armazenamento de dados linear em segmentos de coleção de nós.

Neste exemplo, veremos como você pode usar o array original, as propriedades Offset e Count, e também como você pode percorrer os elementos especificados no 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]);
        }
    }
}

Estrutura ArraySegment - o que eles estavam pensando?

Greg McNulty
fonte
3
ArraySegment é apenas uma estrutura. Meu melhor palpite é que seu propósito é permitir que um segmento de um array seja passado sem ter que fazer uma cópia dele.
Brian
1
Eu acredito que a instrução de condição do loop for deve ser i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 para os fatos que você mencionou, mas @Eren está certo: você não pode iterar os elementos de um segmento assim.
Şafak Gür
3
Geralmente é apropriado dar atribuição quando você usa o código de outra pessoa. São apenas boas maneiras. Seu exemplo se origina de dotnetperls.com/arraysegment .
1
A menos, claro, eles pegaram emprestado de sua resposta. Nesse caso, eles devem lhe dar creds. :)
26

É uma pequena estrutura de soldado insignificante que não faz nada além de manter uma referência a uma matriz e armazena um intervalo de índice. Um pouco perigoso, tome cuidado para que isso não faça uma cópia dos dados do array e não torne o array imutável ou expresse a necessidade de imutabilidade. O padrão de programação mais típico é apenas manter ou passar a matriz e uma variável de comprimento ou parâmetro, como é feito nos métodos .NET BeginRead (), String.SubString (), Encoding.GetString (), etc, etc.

Não é muito usado dentro do .NET Framework, exceto pelo que parece ser um programador específico da Microsoft que trabalhava em sockets da Web e gostava do WCF. O que é provavelmente a orientação adequada, se você gostar, use-a. Ele fez um peek-a-boo no .NET 4.6, o método adicionado MemoryStream.TryGetBuffer () o usa. Preferido a ter dois outargumentos, presumo.

Em geral, a noção mais universal de fatias está no topo da lista de desejos dos principais engenheiros do .NET, como Mads Torgersen e Stephen Toub. Este último deu início à array[:]proposta de sintaxe há um tempo, você pode ver o que eles estão pensando nesta página de Roslyn . Eu presumo que obter suporte CLR é o que depende em última análise. Isso está sendo pensado ativamente para o C # versão 7 afaik, fique de olho no System.Slices .

Atualização: link morto, enviado na versão 7.2 como Span .

Update2: mais suporte no C # versão 8.0 com os tipos Range e Index e um método Slice ().

Hans Passant
fonte
"Não é muito útil '- eu achei incrivelmente útil em um sistema que infelizmente exigia micro otimizações devido à limitação de memória. O fato de que também existem outras soluções" típicas "não diminui sua utilidade
AaronHS
5
Ok, ok, eu realmente não preciso de um depoimento de todos que têm o hábito de usá-lo :) Melhor votar positivamente no comentário de @CRice. Conforme observado, "se você gosta, use-o". Então use. As fatias vão ficar fantásticas, mal posso esperar.
Hans Passant
Existe um ReadOnlySpan para os puristas imutáveis ​​por aí.
Arek Bal,
7

Que tal uma classe wrapper? Apenas para evitar copiar dados para buffers temporais.

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

Exemplo:

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

Ouput:

3
4

3
4
nergeia
fonte
Muito útil amigo, obrigado, observe que você pode implementá-lo IEnumerable<T>e adicionar IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
maio
5

O ArraySegment é MUITO mais útil do que você imagina. Tente executar o seguinte teste de unidade e prepare-se para se surpreender!

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

Veja, tudo o que você precisa fazer é lançar um ArraySegment para IList e ele fará todas as coisas que você provavelmente esperava que fizesse em primeiro lugar. Observe que o tipo ainda é ArraySegment, embora esteja se comportando como uma lista normal.

RESULTADO:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Ben Stabile
fonte
4
É uma pena que seja necessário lançá-lo IList<T>. Eu esperaria que o indexador fosse public.
xmedeko de
2
Para qualquer um que tenha essa resposta e pense que é uma solução milagrosa, recomendo primeiro considerar suas necessidades de desempenho e comparar isso com o acesso direto ao array original usando as restrições de índice do segmento de array. O cast para um IList requer chamadas de método subsequentes (incluindo o indexador) para saltar através da interface IList antes de alcançar a implementação. Existem muitas discussões na Internet em que as pessoas falam sobre o custo de desempenho do uso de chamadas abstratas em loops estreitos. Leia aqui: github.com/dotnet/coreclr/issues/9105
JamesHoux
3

Em palavras simples: ele mantém referência a um array, permitindo que você tenha várias referências a uma única variável de array, cada uma com um intervalo diferente.

Na verdade, ajuda você a usar e passar seções de um array de uma forma mais estruturada, em vez de ter várias variáveis, para manter o índice inicial e o comprimento. Também fornece interfaces de coleção para trabalhar mais facilmente com seções de array.

Por exemplo, os dois exemplos de código a seguir fazem a mesma coisa, um com ArraySegment e outro sem:

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

Obviamente, o primeiro fragmento de código é mais preferido, especialmente quando você deseja passar segmentos de array para uma função.

M.Mahdipour
fonte