CodeGym /Cursos /C# SELF /Memory<T> y <...

Memory<T> y ReadOnlyMemory<T>

C# SELF
Nivel 65 , Lección 4
Disponible

1. Introducción

Todo empezó porque los desarrolladores de .NET querían acelerar el trabajo con grandes volúmenes de datos y dar a los desarrolladores la posibilidad de hacerlo de la forma más cómoda y segura. Primero apareció Span<T>, que es una "ventana" a un bloque contiguo de memoria, capaz de mostrar parte de un array, un string o incluso memoria asignada fuera de .NET (por ejemplo, vía P/Invoke).

Sin embargo, Span<T> tiene una poderosa limitación: siempre debe vivir solo en la pila. No se puede almacenarlo en campos de clases, no se puede devolver desde métodos, no se puede pasar entre métodos asíncronos. La razón es la seguridad: si alguien deja una referencia a una memoria que ya no existe, la aplicación se va a caer.

A veces hace falta devolver slices de datos desde métodos, guardarlos en colecciones o campos de clases, y también usarlos en APIs asíncronas. Ahí entra en escena Memory<T> — en esencia, es la versión segura de "larga vida" de Span<T>, que puede vivir en el heap, pasarse entre hilos, guardarse en propiedades y objetos y comportarse como un objeto .NET normal.

También existe un "hermano mayor" — ReadOnlyMemory<T>, que, como puedes imaginar, no permite modificar los datos subyacentes, pero sí leerlos desde donde sea.

2. ¿En qué se diferencia Span<T> de Memory<T>?

Aquí tienes una pequeña tabla para comparar:

Span<T>
Memory<T>
¿Dónde vive? Sólo en la pila (stack only) En la pila y en el heap (heap/stack)
¿Se puede almacenar en un campo? ❌ No ✅ Sí
¿Se puede devolver desde un método? ❌ No ✅ Sí
Métodos asíncronos/await ❌ No ✅ Sí
Mutable ✅ Existe también ReadOnlySpan<T> ✅ Existe también ReadOnlyMemory<T>
Permite slicear datos ✅ Sí ✅ Sí

Si necesitas recorrer datos rápidamente dentro de un método — usa Span<T>. Si necesitas devolver el resultado hacia afuera o guardarlo en un campo de clase — usa Memory<T>. Y si los datos son sólo de lectura — usa ReadOnlyMemory<T>.

3. Firma y estructura básica de Memory<T>

Como siempre: Memory<T> es genérico. Puedes crear Memory<int>, Memory<byte>, Memory<char> e incluso Memory<MyType>. Dentro de Memory<T> hay una referencia a un array, un string u otra fuente de datos, y además información del rango (desde qué índice y cuántos elementos).

Para obtener acceso rápido desde Memory<T> se usa su propiedad Span — así obtienes inmediatamente un Span<T> que puedes usar dentro de un método sincrónico.

4. Cómo crear Memory<T>: Práctica

Ejemplo 1. Crear desde un array

int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // Todo el array

// Puedes tomar un "slice" — parte del array
Memory<int> slice = memory.Slice(2, 3); // elementos 2, 3 y 4

Ejemplo 2. Crear desde un string (mediante Memory<char>)

string text = "Hello, world!";
Memory<char> charMemory = text.AsMemory(); // Todo el texto como memoria
Memory<char> subMemory = charMemory.Slice(7, 3); // desde el carácter 7, 3 caracteres ("world")

Ejemplo 3. Uso de ReadOnlyMemory<T>

Igual, pero protegido contra cambios:

int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // No permitirá modificar a través de este objeto

5. Conversión entre Memory<T> y Span<T>

No puedes trabajar con Memory<T> con la misma flexibilidad y velocidad que con Span<T> de forma directa — están pensados para cosas distintas. Pero cuando necesites procesar rápido un trozo de memoria, puedes obtener un Span<T> "instantáneo" desde la propiedad Span:

void ProcessData(Memory<int> memory)
{
    Span<int> span = memory.Span;
    for (int i = 0; i < span.Length; i++)
    {
        span[i] += 100;
    }
}

Ten en cuenta: Span<T> funciona solo dentro del método. Si intentas devolverlo afuera, el compilador lanzará un error.

Con ReadOnlyMemory<T> es análogo, solo que obtendrás ReadOnlySpan<T>, que no te deja modificar los datos:

void PrintData(ReadOnlyMemory<int> memory)
{
    ReadOnlySpan<int> roSpan = memory.Span;
    foreach (var item in roSpan)
        Console.WriteLine(item);
}

6. Uso en tareas reales

Procesamiento asíncrono de datos

Aquí es donde Memory<T> brilla. Se puede usar en métodos asíncronos. Por ejemplo, lectura asíncrona de ficheros:

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));
    // Ahora puedes trabajar con buffer
}

Aquí AsMemory pasa el buffer directo al método asíncrono, y no hay problemas de alcance o destrucción de memoria, como pasaría con Span<T>.

Almacenar slices en propiedades y campos

A veces necesitas una clase que guarde "un trozo" de un array grande para usarlo después:

class DataChunk
{
    public Memory<byte> Data { get; }

    public DataChunk(Memory<byte> data)
    {
        Data = data;
    }
}

7. Uso con colecciones, strings y arrays

Con arrays

Lo más habitual:

byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes;          // Todo el array
Memory<byte> part = mem.Slice(2);  // Desde el tercer elemento hasta el final

Con strings

Mediante AsMemory():

string hello = "Hello, Memory!";
ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory"

Con colecciones (por ejemplo, List<T>)

No puedes crear directamente Memory<T> desde un List<T>. Solo a través de un array:

List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // ¡Copia, no referencia!

Cuidado: si quieres evitar copias, mantén los datos en un array.

8. Errores típicos al trabajar con Memory<T>

Error nº1: intentar usar Span<T> en campos de clase. No puedes almacenar Span<T> en campos de clase porque está ligado a la pila. El compilador dará error. Usa Memory<T> para almacenar en el heap.

Error nº2: esperar copia de datos al slicear. Memory<T> no copia datos, crea una "ventana" sobre el array existente. Si cambias datos a través de un Memory<T>, se verá reflejado en todos los que referencian la misma memoria.

Error nº3: intentar crear Memory<T> desde List<T> directamente. Memory<T> solo funciona con arrays, porque List<T> puede mover sus datos en memoria. Convierte la lista a array con ToArray().

Error nº4: ignorar ReadOnlyMemory<T> para datos inmutables. Si los datos no deben cambiar, usa ReadOnlyMemory<T> en vez de Memory<T> para más seguridad.

1
Cuestionario/control
Memoria en C#, nivel 65, lección 4
No disponible
Memoria en C#
Estructura de la memoria en .NET
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION