C# - Apresentando Span<T>

 Hoje vamos apresentar o recurso Span<T> da linguagem C#.

O Span<T> é um novo tipo de valor na plataforma  .NET que permite reduzir a alocação de memória.

Este recurso permite o gerenciamento fortemente tipado da memória contígua, independentemente de como ela foi alocada. Isso facilita a manutenção do código e melhora muito o desempenho dos aplicativos, reduzindo o número de alocações e cópias de memória necessárias. E ele faz isso oferecendo acesso seguro com características de desempenho semelhantes às das matrizes.

Uma struct Span<T> representa uma região contígua de memória arbitrária, e, uma instância de Span<T> é geralmente usada para manter os elementos de uma matriz ou uma parte de uma matriz. Ao contrário de uma matriz, no entanto, uma instância de Span<T> pode apontar para memória gerenciada, memória nativa ou memória gerenciada na pilha.

Dessa forma a utilidade em usar este recurso esta em podermos simplesmente fatiar um pedaço de memória existente e gerenciá-lo,  sem ter que copiá-lo e alocar uma nova memória.

Podemos usar Span com:

A seguir temos uma lista dos tipos que podemos converter para Span<T> :

E usando ReadOnlySpan<T> podemos converter os seguintes tipos :

Principais propriedades :

  1. Empty - Retorna um objeto Span<T> vazio;
  2. IsEmpty - Retorna um valor que indica se o Span<T> atual está vazio.
  3. Item[] - Obtém o elemento no índice baseado em zero especificado.   
  4. Length - Retorna o tamanho do intervalo atual.

Principais métodos :

Exemplos de código usando Span<T>

Na verdade Span<T> é uma estrutura de referência que contém um ponteiro para a memória e o comprimento do intervalo.

Exemplo a seguir estamos criando um array com 10 posições (0 a 9) preenchidos com o valor 0.

A seguir  convertemos o array para Span<byte> e estamos usando o método Slice para fatiar um intervalo que inicia na posição 5 com tamanho 2.

Depois atribuímos valores aos índices 0 e 1 da fatia e fazemos algumas comparações:

      static void Main(string[] args)
        {
            Console.WriteLine("Tecle algo para iniciar");
            Console.ReadKey();

            var vetor = new byte[10];
            // conversão implicita de T[] para Span<T>
            Span<byte> bytes = vetor;

            //define uma fatia do intervalo atual
            //iniciando em 5 com um tamanho de 2

            Span<byte> bytesFatiados = bytes.Slice(start: 5, length: 2);

            //atribui valores às fatias
            bytesFatiados[0] = 42;
            bytesFatiados[1] = 43;

            if( 42 == bytesFatiados[0])
                Console.WriteLine(42);

            if( 43.Equals(bytesFatiados[1]))
                     Console.WriteLine(43);

            if (vetor[5].Equals(bytesFatiados[0]))
                   Console.WriteLine($"{vetor[5]} = {bytesFatiados[0]}");

            if (vetor[6].Equals(bytesFatiados[1]))
                Console.WriteLine($"{vetor[6]} = {bytesFatiados[1]}");

           // lança um IndexOutOfRangeException
            bytesFatiados[2] = 44;
            bytes[2] = 45;            

            if(vetor[2].Equals(bytes[2]))
                Console.WriteLine($"{vetor[2]} = {bytes[2]}");

            if (45.Equals(vetor[2]))
                Console.WriteLine($" 45 = {vetor[2]}");

            Console.ReadKey();
        }

Executando o projeto teremos o resultado a seguir:

A seguir veremos um exemplo onde vamos criar um novo Span e copiar elementos de um Span para outro.

        static void Main(string[] args)
        {
            Console.WriteLine("Tecle algo para iniciar");
            Console.ReadKey();
            int[] arr = { 1, 2, 3, 4, 5 };
            Span<int> span = arr.AsSpan();
            var destino = new Span<int>(new int[arr.Length]);
            span.CopyTo(destino);
            span.Clear();
            Console.WriteLine("Array:");
            foreach (var i in arr)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("Span:");
            foreach (var i in span)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("Span de destino:");
            foreach (var i in destino)
            {
                Console.WriteLine(i);
            }
            Console.ReadKey();
        }

Neste exemplo usamos o método de extensão AsSpan() para converter um array para um Span e a seguir usamos o método CopyTo para copiar o conteúdo de um Span para outro. Depois limpamos o Span usando o método Clear() e exibimos os elementos.

Resultado:

Agora você tem que considerar que como o Span é na verdade uma struct e não herda de IEnumerable, não podemos usar LINQ contra ele. Este é um grande ponto negativo quando comparado às coleções C#.

Outro detalhe é que o Span também possui um número limitado de métodos disponíveis. É muito útil para economizar memória, mas em relação à manipulação de dados para os quais aponta, não podemos fazer muita coisa.

Pegue o projeto aqui:   CShp_Span1.zip

"Os que confiam no SENHOR serão como o monte de Sião, que não se abala, mas permanece para sempre."
Salmos 125:1


Referências:


José Carlos Macoratti