1. Introdução
Tudo começou porque os devs do .NET queriam acelerar o trabalho com grandes volumes de dados e dar aos desenvolvedores a possibilidade de fazer isso da forma mais conveniente e segura. Primeiro surgiu o Span<T>, que é uma "janela" para um bloco contíguo de memória, capaz de mostrar parte de um array, string ou até memória alocada fora do .NET (por exemplo, via P/Invoke).
Mas o Span<T> tem uma grande restrição: ele sempre deve viver apenas na stack. Não dá pra armazená-lo em campos de classes, não dá pra retornar de métodos, não dá pra passar entre métodos assíncronos. A razão é segurança: se alguém mantiver uma referência para uma memória que já não existe, o aplicativo vai cair.
Às vezes é necessário retornar slices de dados de métodos, armazená-los em coleções ou campos de classes, e também usar em APIs assíncronas. É aí que entra o Memory<T> — basicamente, é a versão segura e "de longa vida" do Span<T>, que pode viver no heap, ser passada entre threads, ficar em propriedades e objetos e se comportar como um objeto .NET de respeito.
Tem também o "irmão mais velho" — ReadOnlyMemory<T>, que, como dá pra imaginar, não permite modificar os dados por baixo, mas permite ler eles onde precisar.
2. Qual a diferença entre Span<T> e Memory<T>
Aqui vai uma tabela pequena pra comparar:
|
|
|
|---|---|---|
| Onde vive | Só na stack (stack only) | Na stack e no heap (heap/stack) |
| Pode ser armazenado em campo | ❌ Não | ✅ Sim |
| Pode ser retornado de método | ❌ Não | ✅ Sim |
| Métodos assíncronos/await | ❌ Não pode | ✅ Pode |
| Mutável | ✅ Existe também ReadOnlySpan<T> | ✅ Existe também ReadOnlyMemory<T> |
| Permite fazer slice dos dados | ✅ Sim | ✅ Sim |
Se você precisa "varrer" rapidamente os dados dentro de um método — pega o Span<T>. Se precisa retornar o resultado pra fora ou guardar num campo da classe — usa Memory<T>. E se os dados são só leitura — usa ReadOnlyMemory<T>.
3. Assinatura e estrutura básica do Memory<T>
Como sempre: Memory<T> é genérico, ou seja um tipo universal. Dá pra criar Memory<int>, Memory<byte>, Memory<char> e até Memory<MyType>. Por baixo o Memory<T> guarda uma referência para um array, string ou outra fonte de dados, além de indicar o range (de qual índice e quantos elementos).
Pra obter acesso rápido a partir de Memory<T>, usa-se a propriedade Span — aí você recebe imediatamente um Span<T> que pode usar dentro de um método síncrono.
4. Como criar Memory<T>: prática
Exemplo 1. Criar a partir de um array
int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // Todo o array
// Dá pra pegar um "slice" — parte do array
Memory<int> slice = memory.Slice(2, 3); // elementos 2, 3 e 4
Exemplo 2. Criar a partir de uma string (via Memory<char>)
string text = "Privet, mir!";
Memory<char> charMemory = text.AsMemory(); // Todo o texto como memória
Memory<char> subMemory = charMemory.Slice(7, 3); // a partir do 7º caractere, 3 caracteres ("mir")
Exemplo 3. Usando ReadOnlyMemory<T>
Exatamente igual, só que protegido contra alterações:
int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // Não vai permitir alterar via esse objeto
5. Conversão entre Memory<T> e Span<T>
Trabalhar com Memory<T> tão "flexível" e rápido quanto com Span<T> não é direto — afinal, eles têm propósitos diferentes. Mas quando você precisa processar rápido um pedaço de memória, pode obter um Span<T> "instantâneo" a partir da propriedade Span:
void ProcessData(Memory<int> memory)
{
Span<int> span = memory.Span;
for (int i = 0; i < span.Length; i++)
{
span[i] += 100;
}
}
Repare: o Span<T> funciona apenas dentro do método. Se tentar retorná-lo pra fora, o compilador vai reclamar.
Com ReadOnlyMemory<T> é a mesma ideia, só que você obtém um ReadOnlySpan<T> que não permite modificar os dados:
void PrintData(ReadOnlyMemory<int> memory)
{
ReadOnlySpan<int> roSpan = memory.Span;
foreach (var item in roSpan)
Console.WriteLine(item);
}
6. Uso em tarefas reais
Processamento assíncrono de dados
Aqui o Memory<T> realmente brilha. Dá pra usar em métodos assíncronos! Por exemplo, leitura assíncrona de arquivos:
using System.IO;
using System.Threading.Tasks;
public async Task ReadFileAsync(string path)
{
byte[] buffer = new byte[4096];
using var stream = File.OpenRead(path);
int bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length));
// Agora você pode trabalhar com buffer
}
Aqui AsMemory passa o buffer direto pro método assíncrono, e não tem problema com escopo ou destruição da memória, como seria com Span<T>.
Armazenar slices em propriedades e campos
Às vezes você precisa de uma classe que guarda um "pedaço" de um array grande pra uso posterior:
class DataChunk
{
public Memory<byte> Data { get; }
public DataChunk(Memory<byte> data)
{
Data = data;
}
}
7. Uso com coleções, strings e arrays
Com arrays
Na maioria dos casos:
byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes; // Todo o array
Memory<byte> part = mem.Slice(2); // A partir do terceiro elemento até o fim
Com strings
Via AsMemory():
string hello = "Hello, Memory!";
ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory"
Com coleções (por exemplo, List<T>)
Não dá pra criar um Memory<T> diretamente de um List<T>. Só via array:
List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // Cópia, não referência!
Fica ligado: se quer evitar cópia — mantenha os dados em um array.
8. Erros típicos ao trabalhar com Memory<T>
Erro nº1: tentar usar Span<T> em vez de Memory<T> em campos de classe. Não dá pra armazenar Span<T> em campos de classe porque ele está ligado à stack. O compilador vai dar erro. Use Memory<T> para armazenar no heap.
Erro nº2: achar que slicing faz cópia dos dados. Memory<T> não copia dados, ele cria uma "janela" sobre o array existente. Se você mudar os dados por um Memory<T>, isso vai refletir em todos os outros que referenciam a mesma memória.
Erro nº3: tentar criar Memory<T> diretamente de List<T>. Memory<T> só funciona com arrays, porque List<T> pode mover os dados na memória. Converta a lista para array com ToArray().
Erro nº4: ignorar ReadOnlyMemory<T> para dados imutáveis. Se não precisa alterar os dados, prefira ReadOnlyMemory<T> em vez de Memory<T> para mais segurança.
GO TO FULL VERSION