1. Introducción
Imagínate que tienes un gran array de datos, por ejemplo bytes leídos desde un archivo. Quieres procesar un fragmento: leer una parte como cadena o simplemente pasar una porción de datos a un método. En el estilo "antiguo" copiarías esos bytes a un nuevo array — eso es lento y consume memoria. ¿Y si hay decenas o cientos de esos fragmentos?
Aquí aparece el héroe — Span<T>. Es una estructura especial que describe una vista (view) sobre un array (o cualquier bloque contiguo de memoria) sin copiar datos, simplemente apuntando a la región necesaria. Importante: Span<T> no es una colección nueva, sino una ventana segura sobre un array existente.
Características principales y limitaciones de Span<T>
- Span<T> es una estructura (value type) que representa una "ventana" en la memoria y no copia los elementos.
- Apunta sólo a una región contigua de memoria: un array, parte de un array, un bloque de stackalloc, la memoria de una cadena vía métodos especiales, o memoria dentro de código unsafe.
- Span<T> vive en el stack. No puedes almacenarlo en un campo de clase ni devolverlo directamente desde métodos async.
- Garantiza seguridad de tipos: el acceso a memoria es sin manipular punteros manualmente (si no usas unsafe).
Breve sobre nuevas características de C# 14
C# 14 amplía el trabajo con slices: ranges cómodos .. e índices desde el final ^1, patrones de matching mejorados para arrays y spans y otros "azúcares" sintácticos. Volveremos a esto al final.
2. ¿Cómo crear un Span<T>?
Empecemos con un array sencillo y creemos un span:
int[] numbers = { 10, 20, 30, 40, 50, 60 };
Span<int> midSpan = new Span<int>(numbers, 2, 3); // tomar 3 elementos desde el 3º elemento (índice 2)
Ahora midSpan apunta a los elementos 30, 40, 50 — no es una copia, sino una vista sobre los elementos "vivos" del array. Al modificarlos vía el span, cambias el array original.
midSpan[0] = 100;
Console.WriteLine(numbers[2]); // Mostrará 100
¿Por qué no usar slices de arrays directamente?
El slice clásico con LINQ crea un nuevo array:
int[] slice = numbers.Skip(2).Take(3).ToArray(); // <-- aquí se crea una copia!
Eso es costoso en tiempo y memoria. Span resuelve el problema de copias innecesarias — y funciona no solo con números.
3. Más formas de crear Span<T>
Array existente
Span<int> mySpan = numbers; // Conversión implícita de array a Span
Parte de un array
int[] numbers = {10, 20, 30, 40, 50, 60};
Span<int> part = numbers.AsSpan(1, 4); // 4 elementos empezando en índice 1: {20, 30, 40, 50}
Memoria en el stack (stackalloc)
Span<T> permite alocar arrays en el stack:
Span<byte> buffer = stackalloc byte[128];
for (int i = 0; i < buffer.Length; i++)
buffer[i] = (byte)i;
La memoria del stack es rápida y se libera automáticamente al salir del método. Pero el tamaño debe ser razonable — megabytes en el stack no son aceptables.
Cadenas y ReadOnlySpan<char>
Las cadenas en .NET son inmutables, así que usamos ReadOnlySpan<char>:
string greeting = "Hello, C# world!";
ReadOnlySpan<char> span = greeting.AsSpan(7, 8); // "C# world"
Console.WriteLine(span.ToString()); // C# world
4. ¡Armemos un ejemplo!
using System;
class Program
{
static void Main()
{
int[] orderTotals = { 100, 200, 300, 400, 500, 600, 700 };
Console.WriteLine("Todo el historial de pedidos: ");
foreach (int total in orderTotals)
Console.Write(total + " ");
Console.WriteLine();
Console.WriteLine("Mostrar pedidos del 2º al 4º (índices 1-3):");
Span<int> recentOrders = orderTotals.AsSpan(1, 3);
foreach (int t in recentOrders)
Console.Write(t + " ");
Console.WriteLine();
// Mutamos datos vía Span
recentOrders[1] = 999;
Console.WriteLine("Después del cambio vía Span:");
foreach (int total in orderTotals)
Console.Write(total + " ");
Console.WriteLine();
}
}
La vista recentOrders trabaja directamente con el array — no es una copia.
5. Seguridad, rendimiento y verificaciones
- Uso eficiente de memoria: sin copias innecesarias.
- Comprobaciones de límites protegen contra accesos fuera del array.
- Optimizaciones del JIT dan acceso muy rápido (sin unsafe).
Interacción con métodos
Los métodos pueden aceptar Span<T> o ReadOnlySpan<T>. Si no vas a modificar datos — usa ReadOnlySpan<T>.
static int Sum(Span<int> slice)
{
int sum = 0;
foreach (var item in slice)
sum += item;
return sum;
}
int[] data = { 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine(Sum(data.AsSpan(2, 3))); // 3+4+5=12
6. Uso de ranges
Desde C# 8 están disponibles los ranges .. y los índices desde el final ^ — funcionan de maravilla con slices.
int[] arr = { 10, 20, 30, 40, 50, 60 };
Span<int> span = arr[2..5]; // índices 2,3,4 - es decir 30, 40, 50
En un range el índice de inicio se incluye, el final no.
Índices desde el final
int lastElement = arr[^1]; // último elemento (60)
Span<int> lastTwo = arr[^2..]; // dos últimos elementos (50, 60)
Ranges y cadenas
string code = "SpanMagic!";
var mid = code[4..9]; // "Magic"
Ese slice es un ReadOnlySpan<char>; para obtener una cadena, llama ToString().
7. Patrones modernos para trabajar con slices (C# 14)
Los nuevos patrones simplifican el análisis de arrays y slices.
if (arr is [10, 20, .. var rest]) // .. captura la "cola" del array
{
Console.WriteLine("El inicio coincide, la cola:");
foreach (var x in rest)
Console.WriteLine(x);
}
if (arr is [.., 50, 60])
Console.WriteLine("El array termina en 50, 60");
Comparación: Array, ArraySegment y Span
| Tipo | Mutable | ¿Copia datos? | ¿Se puede en stack? | Clase/estructura | ¿Se puede como campo de clase? |
|---|---|---|---|---|---|
|
sí | - | no | clase | sí |
|
sí | no | no | estructura | sí |
|
sí | no | sí | estructura | no |
|
no | no | sí | estructura | no |
En otras palabras, Span<T> es la evolución de ArraySegment<T>: más rendimiento y más seguro.
8. Errores típicos y trampas
Intentar almacenar un Span<T> en un campo de clase. No puedes: los campos viven en el heap, y Span<T> debe vivir en el stack. Usa ArraySegment<T> o índices para referenciar.
Devolver/almacenar Span<T> desde métodos async. No se puede: la asincronía rompe el stack. Pasa los datos de otra forma — por ejemplo como array, Memory<T>/ReadOnlyMemory<T>.
Rango inválido al crear un slice. Salir de los límites lanza una excepción en tiempo de ejecución. Siempre verifica longitud y límites antes de formar el slice.
Olvidar que el span refleja los datos originales. Cualquier cambio vía Span<T> modifica el array original. Si necesitas una copia independiente — copia los datos explícitamente.
GO TO FULL VERSION