.NET

14 dez, 2021

O recurso Span<T> da linguagem C#

100 visualizações
Publicidade

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:

  • arrays
  • strings
  • alocação de stack
  • buffers nativos

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

  • Arrays
  • Pointers
  • stackalloc
  • intPtr

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

  • Arrays
  • Pointers
  • stackalloc
  • intPtr
  • string

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 :

  • Clear – Limpa o conteúdo deste objeto Span<T>;
  • CopyTo – Copia o conteúdo deste Span<T> para um Span<T> de destino.
  • Fill – Preenche os elementos desse intervalo com um valor especificado.
  • GetEnumerator – Retorna um enumerador para este Span<T>.
  • Slice(int32) – Forma uma fatia com base no intervalo atual que começa em um índice especificado.
  • Slice(int32,int32) – Forma uma fatia com base no intervalo atual que começa em um índice especificado para um tamanho especificado.
  • ToArray – Copia o conteúdo desse intervalo para uma nova matriz.
  • ToString – Retorna a representação de cadeia de caracteres desse objeto Span<T>.
  • TryCopyTo –  Tenta copiar o Span<T> atual para um Span<T> de destino e retorna um valor que indica se a operação de cópia foi bem-sucedida.

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(#8221;{vetor[5]} = {bytesFatiados[0]}”);

            if (vetor[6].Equals(bytesFatiados[1]))
Console.WriteLine(#8221;{vetor[6]} = {bytesFatiados[1]}”);

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

            if(vetor[2].Equals(bytes[2]))
Console.WriteLine(#8221;{vetor[2]} = {bytes[2]}”);

            if (45.Equals(vetor[2]))
Console.WriteLine(#8221; 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