CodeGym /Cursos /C# SELF /Introdução a Span<T>...

Introdução a Span<T> e ReadOnlySpan<T>

C# SELF
Nível 65, Lição 2
Disponível

1. Introdução

Quando você trabalha com arrays, strings e buffers de bytes, muitas vezes aparece a tarefa de "dar uma olhada" numa parte desses dados. Por exemplo, pegar uma substring, fatiar um array, processar parte de um stream de entrada. Em versões antigas do .NET isso exigia ou copiar os dados (criar um novo array/substring), ou escrever código que percorresse o array do índice X até Y. Nada disso é muito agradável nem em desempenho nem em legibilidade.

Aqui vai um exemplo do approach antigo: precisamos passar para um método só uma parte de um array grande:

// Abordagem antiga — copiamos parte do array (ineficiente!)
int[] source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
int[] subArray = source.Skip(2).Take(4).ToArray(); // é criado um novo array

Então, se a gente quer passar eficientemente um "slice" de um array (ou mesmo de uma string) sem criar objetos desnecessários, as ferramentas antigas do C# perdem claramente, especialmente quando trabalhamos com grandes volumes de dados.

É aí que entra o herói do dia — Span<T>!

2. O que é Span<T>? Ideia principal

Span<T> é um tipo que representa uma região contígua de memória de um mesmo tipo T. O objetivo é te dar uma maneira rápida, segura e eficiente de trabalhar com pedaços de arrays, strings, structs e também com memória não-gerenciada (por exemplo, memória alocada fora do runtime do .NET).

A grande sacada do Span<T> é "fatias sem criar novos arrays". Imagine uma régua que permite medir qualquer trecho do mesmo array sem copiar dados e com mínimo risco de errar índices.

Resumo:

  • Span<T> — uma "janela" ou "view" sobre um pedaço de memória que você pode manipular de forma prática e segura.
  • Sem alocação de nova memória — economia de recursos e menos trabalho pro GC.
  • Funciona não só com arrays, mas também com segmentos de strings, blocos alocados por stackalloc e até com memória não-gerenciada.
  • Não pode ser armazenado em campos de classes normais: é um tipo de pilha (stack-only struct).

Por que isso importa?

Em tarefas de alta performance (parsing de arquivos, processamento de grandes buffers, criptografia, serialização) economizar até algumas cópias de arrays pode trazer ganhos enormes de velocidade e reduzir carga no coletor de lixo (GC). E ainda demonstra pros colegas que você manja do C#/.NET moderno!

3. Uso básico do Span<T>: primeiro slice

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Criar um Span sobre parte do array (por exemplo, elementos do índice 2 ao 5 inclusive)
Span<int> middle = new Span<int>(numbers, 2, 4); // índices: 2, 3, 4, 5

// Vemos o subarray: 3, 4, 5, 6
Console.WriteLine(string.Join(", ", middle.ToArray())); // 3, 4, 5, 6

// Modificar o Span modifica o array original!
middle[1] = 999;
Console.WriteLine(numbers[3]); // 999

Importante! Span<T> não copia dados, só aponta para um "pedaço" do array. Todas as mudanças são visíveis tanto no array original quanto no Span.

4. Formas principais de criar Span<T>

Baseado em array:

int[] arr = { 10, 20, 30, 40, 50 };
Span<int> span = arr; // comprimento completo
Span<int> slice = arr.AsSpan(1, 3); // elementos 20, 30, 40

Baseado em parte do array:

Span<int> part = new Span<int>(arr, 2, 2); // elementos 30, 40

stackalloc: alocação na stack (extremamente rápida e não vai pro heap):

Span<byte> buffer = stackalloc byte[128];
buffer[0] = 42;

Usando o método .Slice():

Span<int> subSpan = span.Slice(1, 2); // elementos 20, 30

Esquema visual do "slice"


Array original:  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
                           <--- Span: 3, 4, 5, 6 --->

5. Restrições e particularidades do Span<T>

  • Stack-only! Não pode ser guardado em campos "normais" de classe nem fazer parte de closures — é um tipo de pilha.
  • Não pode ser usado como campo de classe ou retornado de métodos async (o compilador vai reclamar).
  • Não pode ser capturado em lambdas/métodos anônimos — use "aqui e agora".
  • Não dá pra serializar diretamente nem mandar entre threads.

Isso acontece porque Span pode apontar pra qualquer região de memória, e se ele "migrar" pro heap pode ocorrer condição insegura.

6. Imutabilidade: ReadOnlySpan<T>

Às vezes você quer apenas "olhar" pra parte da memória, sem permitir mudanças. Para isso existe a variante imutável — ReadOnlySpan<T>.

string text = "Hello, Span!";
ReadOnlySpan<char> letters = text.AsSpan(7, 4); // 'S', 'p', 'a', 'n'
Console.WriteLine(string.Join(", ", letters.ToArray())); // S, p, a, n

// letters[0] = 'Z'; // Erro: indexador somente leitura!

Um cenário clássico — passar com segurança um "pedaço" de string ou array pra algum lugar onde não se deve (e não se quer) modificar os dados.

7. Exemplo prático: fatiando arrays e strings

Suponha um analisador de dados que de uma string grande extrai uma substring, busca números nela e retorna a soma (sem cópias desnecessárias no fatiamento inicial):

using System;

class Program
{
    static void Main()
    {
        // Suponha que o usuário digitou uma string grande de números separados por espaço
        string input = "12 34 56 78 90 123 456 789";
        // Precisamos calcular a soma dos números só do "centro", por exemplo, 56 78 90

        // Extraímos a substring (mas não a copiamos!)
        ReadOnlySpan<char> center = input.AsSpan(6, 8); // índices podem ser calculados dinamicamente

        // Fazemos parse dos números via Split (cria um array temporário)
        string[] numbers = center.ToString().Split(' ');
        int sum = 0;
        foreach (var str in numbers)
        {
            if (int.TryParse(str, out int num))
                sum += num;
        }
        Console.WriteLine($"Soma dos números centrais: {sum}");
    }
}

Bibliotecas modernas de parsing CSV e JSON fazem uso de Span para alta velocidade ao trabalhar com grandes volumes de dados textuais — agora você sabe qual é a base do "truque".

8. Dicas úteis

Span vs cópia de arrays

// Forma antiga: copiamos um pedaço do array
int[] arr = Enumerable.Range(0, 1000000).ToArray();
int[] firstThousand = arr.Take(1000).ToArray(); // alocou um novo array de 1000 elementos

// Forma nova: Span
Span<int> bestThousand = arr.AsSpan(0, 1000); // não copiamos nada!
bestThousand[0] = 42; // muda também em arr

A diferença fica bem clara em parsing intensivo, processamento de buffers de rede, trabalho com dados binários.

Aplicações reais: por que conhecer Span

  • Parsing de alta performance e processamento de dados textuais/binários. Bibliotecas modernas de serialização (por exemplo, System.Text.Json, Span na documentação da Microsoft) usam Span pra acelerar processamento.
  • Buffering e leitura de arquivos (fatiando grandes buffers sem copiar).
  • Trabalhar com memória limitada (embedded, IoT) — documentação oficial sobre Memory/Spans.
  • Algoritmos para imagem e áudio, onde velocidade e evitar alocações extras são críticos.
  • Acelerar parsing de CSV, JSON, XML usando Span — especialmente no .NET 8/9.

Em entrevistas perguntas sobre Span apareceram desde que ele surgiu no .NET Core 2.1+, e no .NET 9 virou cada vez mais esperado que você conheça o assunto.

Esquema visual: onde está o Span e onde está o array


+--------------------+
|   int[]  array     |
|  1 2 3 4 5 6 7 8   |
+--------------------+
       ^       ^
       |       |
   [ 2, 3, 4, 5 ]  <-- Span<int> "janela de memória" (slice)

Span<T> não é um array separado, é uma "lente transparente" sobre parte dos dados.

Diferença para outras collections: comparação

Tipo Armazena dados? É possível alterar elementos? É possível mudar tamanho? Copia ao fatiar? Onde vive?
int[]
Sim Sim Não Sim (via .Take) Heap
List<int>
Sim Sim Sim Sim Heap
Span<int>
Não Sim Não Não Stack
ReadOnlySpan<int>
Não Não Não Não Stack

9. Erros comuns ao trabalhar com Span/ReadOnlySpan

Erro nº1: tentar salvar um Span como campo de classe. O compilador vai dar erro "Span type may not be used in this context". Isso é intencional: armazenar um Span em campo é inseguro.

Erro nº2: retornar um Span de um método async. Não faça isso, porque métodos async podem "escapar" pro heap. Use um array ou outro tipo em vez disso.

Erro nº3: esquecer que mudanças via Span afetam o array original. Isso pode alterar dados "lá fora" inesperadamente e causar comportamento imprevisível.

2
Tarefa
C# SELF, nível 65, lição 2
Bloqueado
Modificação usando `Span`
Modificação usando `Span`
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION