Saltar al pie de página
.NET AYUDA

Span C# (Cómo Funciona para Desarrolladores)

Span es un tipo introducido en C# 7.2 como parte de la estructura Span en el espacio de nombres System. Está diseñado para representar una región contigua de memoria arbitraria. A diferencia de los arreglos o colecciones como el montón manejado, Span no posee la memoria de la pila o la región de memoria a la que apunta; en su lugar, proporciona una vista ligera sobre bloques de memoria existentes. Esta característica hace que Span sea particularmente potente en escenarios donde necesitas trabajar con búferes de memoria de manera eficiente sin incurrir en costos adicionales y en situaciones de código inseguro. Más adelante en este artículo, también veremos la introducción a la biblioteca IronPDF de Iron Software.

Características principales de Span

1. Gestión de memoria

Span en C# permite a los desarrolladores trabajar directamente con la memoria sin recurrir a asignaciones tradicionales de montón. Ofrece una forma de crear trozos de memoria a partir de arreglos existentes u otras fuentes de memoria, eliminando la necesidad de copias de memoria adicionales.

2. Abstracciones de copia cero

Una de las características destacadas del Span de C# son sus abstracciones de cero copias. En lugar de duplicar datos, Span proporciona una forma de referenciar la memoria existente de manera eficiente. Esto es particularmente beneficioso en escenarios donde copiar grandes cantidades de datos sería poco práctico o demasiado costoso.

3. Operaciones con punteros

Aunque C# ha sido tradicionalmente un lenguaje de alto nivel y seguro, Span introduce un grado de manipulación de memoria de bajo nivel similar a trabajar con punteros en lenguajes como C o C++. Los desarrolladores pueden realizar operaciones similares a punteros sin sacrificar la seguridad y la naturaleza gestionada de C#.

4. Naturaleza inmutable

A pesar de sus capacidades para el acceso a memoria de bajo nivel, Span de C# sigue siendo inmutable. Esto significa que, aunque permite manipular memoria, garantiza la seguridad al prevenir modificaciones no intencionadas.

Ejemplo

using System;

class Program
{
    static void Main()
    {
        int[] array = { 1, 2, 3, 4, 5 };

        // Create a span that points to the entire array
        Span<int> span = array;

        // Modify the data using the span
        span[2] = 10;

        // Print the modified array
        foreach (var item in array)
        {
            Console.WriteLine(item);
        }
    }
}
using System;

class Program
{
    static void Main()
    {
        int[] array = { 1, 2, 3, 4, 5 };

        // Create a span that points to the entire array
        Span<int> span = array;

        // Modify the data using the span
        span[2] = 10;

        // Print the modified array
        foreach (var item in array)
        {
            Console.WriteLine(item);
        }
    }
}
Imports System

Friend Class Program
	Shared Sub Main()
		Dim array() As Integer = { 1, 2, 3, 4, 5 }

		' Create a span that points to the entire array
		Dim span As Span(Of Integer) = array

		' Modify the data using the span
		span(2) = 10

		' Print the modified array
		For Each item In array
			Console.WriteLine(item)
		Next item
	End Sub
End Class
$vbLabelText   $csharpLabel

SóloLectura

Mientras que Span es mutable y permite modificaciones en los datos subyacentes, ReadOnlySpan es una vista inmutable de la memoria. Proporciona una interfaz de solo lectura a una región contigua de memoria, lo que la hace adecuada para escenarios en los que solo necesitas leer los datos sin modificarlos.

Aquí hay algunos puntos clave.

1. Vista de sólo lectura

Como su nombre indica, ReadOnlySpan te permite crear una vista de solo lectura de un bloque de memoria. Esto significa que no puedes modificar los elementos a través de un ReadOnlySpan.

2. Representación de la memoria

Al igual que Span, ReadOnlySpan no posee la memoria a la que apunta. Se refiere a la memoria existente y puede apuntar a arreglos, memoria asignada en la pila o memoria nativa.

3. Ventajas de rendimiento

Como Span, ReadOnlySpan puede ofrecer un mejor rendimiento en comparación con los tipos de colecciones tradicionales, especialmente al tratar con grandes cantidades de datos, ya que reduce la necesidad de copiar.

4. Sin comprobación de límites

Como con Span, ReadOnlySpan no realiza comprobaciones de límites. Es responsabilidad del desarrollador asegurarse de que las operaciones se mantengan dentro de los límites de la memoria subyacente.

5. Uso con Array Slicing

ReadOnlySpan admite el corte, permitiéndote crear sub-spans que referencian una porción de la memoria original.

Ejemplo

using System;

class Program
{
    static void Main()
    {
        int[] array = { 1, 2, 3, 4, 5 };

        // Create a read-only span that points to the entire array
        ReadOnlySpan<int> readOnlySpan = array;

        // Access and print the data through the read-only span
        foreach (var item in readOnlySpan)
        {
            Console.WriteLine(item);
        }

        // Note: The following line would result in a compilation error since readOnlySpan is read-only.
        // readOnlySpan[2] = 10;
    }
}
using System;

class Program
{
    static void Main()
    {
        int[] array = { 1, 2, 3, 4, 5 };

        // Create a read-only span that points to the entire array
        ReadOnlySpan<int> readOnlySpan = array;

        // Access and print the data through the read-only span
        foreach (var item in readOnlySpan)
        {
            Console.WriteLine(item);
        }

        // Note: The following line would result in a compilation error since readOnlySpan is read-only.
        // readOnlySpan[2] = 10;
    }
}
Imports System

Friend Class Program
	Shared Sub Main()
		Dim array() As Integer = { 1, 2, 3, 4, 5 }

		' Create a read-only span that points to the entire array
		Dim readOnlySpan As ReadOnlySpan(Of Integer) = array

		' Access and print the data through the read-only span
		For Each item In readOnlySpan
			Console.WriteLine(item)
		Next item

		' Note: The following line would result in a compilation error since readOnlySpan is read-only.
		' readOnlySpan[2] = 10;
	End Sub
End Class
$vbLabelText   $csharpLabel

Existen muchas maneras diferentes de crear ReadOnlySpan y trabajar con él. Ejemplos a continuación.

1. Creación de ReadOnlySpan a partir de String

string msg = "Hello, World!";
ReadOnlySpan<char> span1 = msg.AsSpan();
// Read-only manipulation
char firstChar = span1[0];
Console.WriteLine(firstChar); // Outputs: H
string msg = "Hello, World!";
ReadOnlySpan<char> span1 = msg.AsSpan();
// Read-only manipulation
char firstChar = span1[0];
Console.WriteLine(firstChar); // Outputs: H
Dim msg As String = "Hello, World!"
Dim span1 As ReadOnlySpan(Of Char) = msg.AsSpan()
' Read-only manipulation
Dim firstChar As Char = span1(0)
Console.WriteLine(firstChar) ' Outputs: H
$vbLabelText   $csharpLabel

2. Trabajar con subcadenas

Usar Slice en el ReadOnlySpan

// Example usage of Slice method on ReadOnlySpan<char>
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan();
ReadOnlySpan<char> substringSpan = spanFromString.Slice(7, 6); // Extracts 'String'
// Example usage of Slice method on ReadOnlySpan<char>
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan();
ReadOnlySpan<char> substringSpan = spanFromString.Slice(7, 6); // Extracts 'String'
' Example usage of Slice method on ReadOnlySpan<char>
Dim spanFromString As ReadOnlySpan(Of Char) = "Sample String".AsSpan()
Dim substringSpan As ReadOnlySpan(Of Char) = spanFromString.Slice(7, 6) ' Extracts 'String'
$vbLabelText   $csharpLabel

3. Pasar una subcadena a un método

Pasar ReadOnlySpan como parámetro al método.

void ProcessSubstringfromReadOnlySpan(ReadOnlySpan<char> substring)
{
    // Perform operations on the substring
}

// Usage
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan();
ProcessSubstringfromReadOnlySpan(spanFromString.Slice(7, 6));
void ProcessSubstringfromReadOnlySpan(ReadOnlySpan<char> substring)
{
    // Perform operations on the substring
}

// Usage
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan();
ProcessSubstringfromReadOnlySpan(spanFromString.Slice(7, 6));
Private Sub ProcessSubstringfromReadOnlySpan(ByVal substring As ReadOnlySpan(Of Char))
	' Perform operations on the substring
End Sub

' Usage
Private spanFromString As ReadOnlySpan(Of Char) = "Sample String".AsSpan()
ProcessSubstringfromReadOnlySpan(spanFromString.Slice(7, 6))
$vbLabelText   $csharpLabel

4. Búsqueda dentro de una cadena

ReadOnlySpan para buscar dentro de una cadena con IndexOf().

ReadOnlySpan<char> stringSpan = "Hello, World!".AsSpan();
int index = stringSpan.IndexOf('W');
Console.WriteLine(index); // Outputs: 7
ReadOnlySpan<char> stringSpan = "Hello, World!".AsSpan();
int index = stringSpan.IndexOf('W');
Console.WriteLine(index); // Outputs: 7
Dim stringSpan As ReadOnlySpan(Of Char) = "Hello, World!".AsSpan()
Dim index As Integer = stringSpan.IndexOf("W"c)
Console.WriteLine(index) ' Outputs: 7
$vbLabelText   $csharpLabel

5. Uso de archivos mapeados en memoria

ReadOnlySpan puede ser más eficiente con archivos memory-mapped.

using System;
using System.IO.MemoryMappedFiles;

class Program
{
    static void ProcessData(ReadOnlySpan<byte> data)
    {
        // Process data directly from the memory-mapped file
    }

    static void Main()
    {
        using (var memmf = MemoryMappedFile.CreateFromFile("data.bin"))
        {
            using (var accessor = memmf.CreateViewAccessor())
            {
                byte[] buffer = new byte[accessor.Capacity];
                accessor.ReadArray(0, buffer, 0, buffer.Length);

                ReadOnlySpan<byte> dataSpan = new ReadOnlySpan<byte>(buffer);
                ProcessData(dataSpan);
            }
        }
    }
}
using System;
using System.IO.MemoryMappedFiles;

class Program
{
    static void ProcessData(ReadOnlySpan<byte> data)
    {
        // Process data directly from the memory-mapped file
    }

    static void Main()
    {
        using (var memmf = MemoryMappedFile.CreateFromFile("data.bin"))
        {
            using (var accessor = memmf.CreateViewAccessor())
            {
                byte[] buffer = new byte[accessor.Capacity];
                accessor.ReadArray(0, buffer, 0, buffer.Length);

                ReadOnlySpan<byte> dataSpan = new ReadOnlySpan<byte>(buffer);
                ProcessData(dataSpan);
            }
        }
    }
}
Imports System
Imports System.IO.MemoryMappedFiles

Friend Class Program
	Private Shared Sub ProcessData(ByVal data As ReadOnlySpan(Of Byte))
		' Process data directly from the memory-mapped file
	End Sub

	Shared Sub Main()
		Using memmf = MemoryMappedFile.CreateFromFile("data.bin")
			Using accessor = memmf.CreateViewAccessor()
				Dim buffer(accessor.Capacity - 1) As Byte
				accessor.ReadArray(0, buffer, 0, buffer.Length)

				Dim dataSpan As New ReadOnlySpan(Of Byte)(buffer)
				ProcessData(dataSpan)
			End Using
		End Using
	End Sub
End Class
$vbLabelText   $csharpLabel

6. Manipulación eficiente de cadenas

ReadOnlySpan se puede usar para una manipulación eficiente de cadenas.

Span<char> newSpan = new char[6];
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan().Slice(7, 6);
spanFromString.CopyTo(newSpan);
Console.WriteLine(new string(newSpan)); // Outputs: String
Span<char> newSpan = new char[6];
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan().Slice(7, 6);
spanFromString.CopyTo(newSpan);
Console.WriteLine(new string(newSpan)); // Outputs: String
Dim newSpan As Span(Of Char) = New Char(5){}
Dim spanFromString As ReadOnlySpan(Of Char) = "Sample String".AsSpan().Slice(7, 6)
spanFromString.CopyTo(newSpan)
Console.WriteLine(New String(newSpan)) ' Outputs: String
$vbLabelText   $csharpLabel

7. Pasar subcadena a API

Cuando se trabaja con bibliotecas o APIs externas que operan con spans de caracteres.

void ExternalApiMethod(ReadOnlySpan<char> data)
{
    // Call the external API with the character span
}

// Usage
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan();
ExternalApiMethod(spanFromString.Slice(7, 6));
void ExternalApiMethod(ReadOnlySpan<char> data)
{
    // Call the external API with the character span
}

// Usage
ReadOnlySpan<char> spanFromString = "Sample String".AsSpan();
ExternalApiMethod(spanFromString.Slice(7, 6));
Private Sub ExternalApiMethod(ByVal data As ReadOnlySpan(Of Char))
	' Call the external API with the character span
End Sub

' Usage
Private spanFromString As ReadOnlySpan(Of Char) = "Sample String".AsSpan()
ExternalApiMethod(spanFromString.Slice(7, 6))
$vbLabelText   $csharpLabel

ReadOnlySpan proporciona una forma de trabajar con cadenas más eficiente, especialmente en escenarios donde las asignaciones de memoria y el copiar deberían minimizarse. Es una herramienta poderosa para optimizar el código crítico para el rendimiento y puede ser particularmente beneficiosa al tratar grandes cantidades de datos de cadenas.

Limitaciones de espacio

Aunque Span en C# es una característica poderosa con numerosas ventajas, también presenta ciertas limitaciones y consideraciones, especialmente en el contexto de la memoria contigua y no contigua. Exploremos estas limitaciones:

1. Búferes de memoria contigua

1.1 Sin gestión automática de memoria

Span no gestiona la memoria a la que apunta. Esto significa que si la memoria administrada subyacente se libera o sale del alcance, usar el Span resultará en un comportamiento indefinido o posibles fallos. Los desarrolladores deben asegurarse de que la memoria subyacente todavía sea válida al usar un Span.

1.2 Sin recolección de elementos no utilizados

Dado que Span no posee memoria, no está sujeta a la recolección de basura. Por lo tanto, debes tener cuidado al trabajar con memoria asignada en la pila o memoria que tiene una vida útil más corta que el Span mismo.

1.3 La comprobación de límites está desactivada

Span y ReadOnlySpan no realizan comprobaciones de límites por defecto. Esto puede llevar a acceder a ubicaciones de memoria no válidas si no se usa con cuidado. Los desarrolladores deben asegurarse manualmente de que las operaciones en un Span se mantengan dentro de los límites de la memoria subyacente.

1.4 Sin soporte para memoria no contigua

Span está diseñado para trabajar con memoria contigua. Si tienes memoria no contigua o necesitas representar estructuras de datos más complejas, Span puede no ser la opción más apropiada.

1.5 No todas las operaciones son compatibles

Mientras que Span admite muchas operaciones comunes como corte, indexación e iteración, no todas las operaciones son compatibles. Por ejemplo, no puedes cambiar el tamaño de un Span, y ciertas operaciones que implican cambiar la longitud de la memoria subyacente no están permitidas.

1.6 Compatibilidad limitada de plataformas

Aunque Span es parte del .NET Standard y .NET Core, puede que no esté disponible en todas las plataformas o entornos. Es crucial asegurarse de que tu plataforma de destino admita Span si planeas usarlo en tu código.

2. Búferes de memoria no contiguos

2.1 Soporte limitado para memoria no contigua

ReadOnlySpan está diseñado principalmente para trabajar de manera fluida con bloques de memoria o búferes contiguos. Puede que no sea la opción más adecuada para escenarios donde están involucrados búferes de memoria no contigua o estructuras con huecos en la memoria.

2.2 Limitaciones estructurales

Ciertas estructuras de datos o escenarios que dependen de memoria no contigua pueden no alinearse bien con ReadOnlySpan. Por ejemplo, estructuras de datos como listas enlazadas o estructuras de gráfico pueden no ser adecuadas debido al requisito de memoria contigua de ReadOnlySpan.

2.3 Operaciones complejas con punteros

En situaciones que involucran memoria no contigua, particularmente aquellas que requieren aritmética de punteros intrincada, ReadOnlySpan podría no ofrecer el mismo control y flexibilidad de bajo nivel que los punteros en lenguajes como C++. En tales casos, podría ser más apropiado utilizar código inseguro con punteros.

2.4 Falta de soporte directo en algunas API

Al igual que la memoria contigua, es importante notar que no todas las API o bibliotecas pueden admitir directamente la memoria no contigua representada por ReadOnlySpan. Adaptarse a tales escenarios podría requerir pasos o conversiones intermedias adicionales para garantizar la compatibilidad.

Span y memoria no gestionada

En C#, Span puede ser utilizado eficazmente con memoria no gestionada para realizar operaciones relacionadas con la memoria de manera controlada y eficiente. Memoria no gestionada se refiere a la memoria que no es gestionada por el recolector de basura del runtime de .NET, y a menudo involucra el uso de asignaciones y desasignaciones de memoria nativa. Aquí se explica cómo puede utilizarse Span con memoria no gestionada en C#.

Asignación de memoria no gestionada

Para asignar memoria no gestionada, puedes usar la clase System.Runtime.InteropServices.MemoryMarshal. El método Marshal.AllocHGlobal asigna memoria y devuelve un puntero al bloque asignado. La memoria asignada o la dirección de memoria se mantiene en un puntero unmanagedMemory y tendrá acceso de lectura-escritura. Las regiones contiguas de memoria pueden ser fácilmente accesadas.

using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        const int bufferSize = 100;
        IntPtr unmanagedMemory = Marshal.AllocHGlobal(bufferSize);

        // Create a Span from the unmanaged memory
        Span<byte> span = new Span<byte>(unmanagedMemory.ToPointer(), bufferSize);

        // Use the Span as needed...

        // Don't forget to free the unmanaged memory when done
        Marshal.FreeHGlobal(unmanagedMemory);
    }
}
using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        const int bufferSize = 100;
        IntPtr unmanagedMemory = Marshal.AllocHGlobal(bufferSize);

        // Create a Span from the unmanaged memory
        Span<byte> span = new Span<byte>(unmanagedMemory.ToPointer(), bufferSize);

        // Use the Span as needed...

        // Don't forget to free the unmanaged memory when done
        Marshal.FreeHGlobal(unmanagedMemory);
    }
}
Imports System
Imports System.Runtime.InteropServices

Friend Class Program
	Shared Sub Main()
		Const bufferSize As Integer = 100
		Dim unmanagedMemory As IntPtr = Marshal.AllocHGlobal(bufferSize)

		' Create a Span from the unmanaged memory
		Dim span As New Span(Of Byte)(unmanagedMemory.ToPointer(), bufferSize)

		' Use the Span as needed...

		' Don't forget to free the unmanaged memory when done
		Marshal.FreeHGlobal(unmanagedMemory)
	End Sub
End Class
$vbLabelText   $csharpLabel

En el código fuente anterior, asignamos un bloque de memoria no gestionada usando Marshal.AllocHGlobal y luego creamos un Span<byte> usando el puntero obtenido de la memoria no gestionada. Esto nos permite trabajar con memoria no gestionada usando la API familiar de Span. Es importante notar que al trabajar con memoria no gestionada, eres responsable de gestionar la asignación y desasignación de la memoria.

Copiar datos desde y hacia memoria no gestionada

Span proporciona métodos como Slice, CopyTo y ToArray que se pueden usar para copiar datos entre memoria gestionada y no gestionada de manera eficiente.

using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        // Managed array to copy data from
        int[] sourceArray = { 1, 2, 3, 4, 5 };

        // Allocate unmanaged memory for the destination data
        IntPtr destinationPointer = Marshal.AllocHGlobal(sourceArray.Length * sizeof(int));
        try
        {
            // Create a Span<int> from the source array
            Span<int> sourceSpan = sourceArray;

            // Create a Span<int> from the allocated unmanaged memory
            Span<int> destinationSpan = new Span<int>(destinationPointer.ToPointer(), sourceArray.Length);

            // Copy data from the source Span<int> to the destination Span<int>
            sourceSpan.CopyTo(destinationSpan);

            // Print the values in the destination memory
            Console.WriteLine("Values in the destination memory:");
            foreach (var value in destinationSpan)
            {
                Console.Write($"{value} ");
            }
        }
        finally
        {
            // Deallocate the unmanaged memory when done
            Marshal.FreeHGlobal(destinationPointer);
        }
    }
}
using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        // Managed array to copy data from
        int[] sourceArray = { 1, 2, 3, 4, 5 };

        // Allocate unmanaged memory for the destination data
        IntPtr destinationPointer = Marshal.AllocHGlobal(sourceArray.Length * sizeof(int));
        try
        {
            // Create a Span<int> from the source array
            Span<int> sourceSpan = sourceArray;

            // Create a Span<int> from the allocated unmanaged memory
            Span<int> destinationSpan = new Span<int>(destinationPointer.ToPointer(), sourceArray.Length);

            // Copy data from the source Span<int> to the destination Span<int>
            sourceSpan.CopyTo(destinationSpan);

            // Print the values in the destination memory
            Console.WriteLine("Values in the destination memory:");
            foreach (var value in destinationSpan)
            {
                Console.Write($"{value} ");
            }
        }
        finally
        {
            // Deallocate the unmanaged memory when done
            Marshal.FreeHGlobal(destinationPointer);
        }
    }
}
Imports System
Imports System.Runtime.InteropServices

Friend Class Program
	Shared Sub Main()
		' Managed array to copy data from
		Dim sourceArray() As Integer = { 1, 2, 3, 4, 5 }

		' Allocate unmanaged memory for the destination data
		Dim destinationPointer As IntPtr = Marshal.AllocHGlobal(sourceArray.Length * Len(New Integer()))
		Try
			' Create a Span<int> from the source array
			Dim sourceSpan As Span(Of Integer) = sourceArray

			' Create a Span<int> from the allocated unmanaged memory
			Dim destinationSpan As New Span(Of Integer)(destinationPointer.ToPointer(), sourceArray.Length)

			' Copy data from the source Span<int> to the destination Span<int>
			sourceSpan.CopyTo(destinationSpan)

			' Print the values in the destination memory
			Console.WriteLine("Values in the destination memory:")
			For Each value In destinationSpan
				Console.Write($"{value} ")
			Next value
		Finally
			' Deallocate the unmanaged memory when done
			Marshal.FreeHGlobal(destinationPointer)
		End Try
	End Sub
End Class
$vbLabelText   $csharpLabel

En este ejemplo:

  • Marshal.AllocHGlobal asigna memoria no gestionada para los datos de destino.
  • new Span<int>(destinationPointer.ToPointer(), sourceArray.Length) crea un Span<int> a partir de la memoria no gestionada asignada.
  • El método sourceSpan.CopyTo(destinationSpan) copia los datos desde el arreglo gestionado a la memoria no gestionada.
  • Los valores en la memoria de destino se imprimen para verificar la operación de copia.
  • El método Marshal.FreeHGlobal(destinationPointer) se usa para desasignar la memoria no gestionada cuando se termina.

Uso de código no seguro

Al tratar con memoria no gestionada, también podrías usar código inseguro con punteros. En tales casos, puedes obtener un puntero del Span usando el método Unsafe.AsPointer().

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

class Program
{
    static void Main()
    {
        const int bufferSize = 100;
        IntPtr unmanagedMemory = Marshal.AllocHGlobal(bufferSize);

        // Create a Span from the unmanaged memory
        Span<byte> span = new Span<byte>(unmanagedMemory.ToPointer(), bufferSize);

        // Use unsafe code to work with pointers
        unsafe
        {
            byte* pointer = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
            // Use the pointer as needed...
        }

        // Don't forget to free the unmanaged memory when done
        Marshal.FreeHGlobal(unmanagedMemory);
    }
}
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

class Program
{
    static void Main()
    {
        const int bufferSize = 100;
        IntPtr unmanagedMemory = Marshal.AllocHGlobal(bufferSize);

        // Create a Span from the unmanaged memory
        Span<byte> span = new Span<byte>(unmanagedMemory.ToPointer(), bufferSize);

        // Use unsafe code to work with pointers
        unsafe
        {
            byte* pointer = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
            // Use the pointer as needed...
        }

        // Don't forget to free the unmanaged memory when done
        Marshal.FreeHGlobal(unmanagedMemory);
    }
}
Imports System
Imports System.Runtime.InteropServices
Imports System.Runtime.CompilerServices

Friend Class Program
	Shared Sub Main()
		Const bufferSize As Integer = 100
		Dim unmanagedMemory As IntPtr = Marshal.AllocHGlobal(bufferSize)

		' Create a Span from the unmanaged memory
		Dim span As New Span(Of Byte)(unmanagedMemory.ToPointer(), bufferSize)

		' Use unsafe code to work with pointers
'INSTANT VB TODO TASK: C# 'unsafe' code is not converted by Instant VB:
'		unsafe
'		{
'			byte* pointer = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
'			' Use the pointer as needed...
'		}

		' Don't forget to free the unmanaged memory when done
		Marshal.FreeHGlobal(unmanagedMemory)
	End Sub
End Class
$vbLabelText   $csharpLabel

En este ejemplo, usamos el método Unsafe.AsPointer para obtener un puntero del Span. Esto nos permite usar código inseguro cuando trabajamos directamente con punteros.

Recuerda, al trabajar con memoria no gestionada, es crucial gestionar adecuadamente la asignación y desasignación para evitar fugas de memoria. Libera siempre la memoria no gestionada usando métodos apropiados, como Marshal.FreeHGlobal(). Además, ejerce precaución al usar código inseguro, ya que puede introducir riesgos potenciales de seguridad si no se maneja adecuadamente.

Span y llamadas a métodos asíncronos

Usar Span junto con llamadas asíncronas a métodos en C# es una combinación poderosa, especialmente al tratar con grandes cantidades de datos o operaciones de E/S. El objetivo es manejar operaciones asíncronas sin copias innecesarias de datos de manera eficiente. Exploramos cómo puedes aprovechar Span en escenarios asíncronos:

1. Operaciones de E/S asíncronas:

Al tratar con operaciones de I/O asincrónicas, como leer o escribir datos en un flujo, puedes usar Memory o Span para trabajar con los datos de manera eficiente sin crear buffers adicionales.

using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task ProcessDataAsync(Stream stream)
    {
        const int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];
        while (true)
        {
            int bytesRead = await stream.ReadAsync(buffer.AsMemory());
            if (bytesRead == 0)
                break;

            // Process the data using Span without unnecessary copying
            ProcessData(buffer.AsSpan(0, bytesRead));
        }
    }

    static void ProcessData(Span<byte> data)
    {
        // Perform operations on the data
    }
}
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task ProcessDataAsync(Stream stream)
    {
        const int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];
        while (true)
        {
            int bytesRead = await stream.ReadAsync(buffer.AsMemory());
            if (bytesRead == 0)
                break;

            // Process the data using Span without unnecessary copying
            ProcessData(buffer.AsSpan(0, bytesRead));
        }
    }

    static void ProcessData(Span<byte> data)
    {
        // Perform operations on the data
    }
}
Imports System
Imports System.IO
Imports System.Threading.Tasks

Friend Class Program
	Private Shared Async Function ProcessDataAsync(ByVal stream As Stream) As Task
		Const bufferSize As Integer = 4096
		Dim buffer(bufferSize - 1) As Byte
		Do
			Dim bytesRead As Integer = Await stream.ReadAsync(buffer.AsMemory())
			If bytesRead = 0 Then
				Exit Do
			End If

			' Process the data using Span without unnecessary copying
			ProcessData(buffer.AsSpan(0, bytesRead))
		Loop
	End Function

	Private Shared Sub ProcessData(ByVal data As Span(Of Byte))
		' Perform operations on the data
	End Sub
End Class
$vbLabelText   $csharpLabel

En este ejemplo, el método ReadAsync lee asíncronamente datos desde un flujo hacia el buffer. El método ProcessData luego procesa los datos directamente desde el Span sin copiarlos a otro buffer.

2. Operaciones de archivos asíncronas:

Similar a las operaciones de E/S, al tratar con operaciones asíncronas de archivos, puedes usar Span para procesar datos de manera eficiente sin copias adicionales.

using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task ProcessFileAsync(string filePath)
    {
        const int bufferSize = 4096;
        using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            byte[] buffer = new byte[bufferSize];
            while (true)
            {
                int bytesRead = await fileStream.ReadAsync(buffer.AsMemory());
                if (bytesRead == 0)
                    break;

                // Process the data using Span without unnecessary copying
                ProcessData(buffer.AsSpan(0, bytesRead));
            }
        }
    }

    static void ProcessData(Span<byte> data)
    {
        // Perform operations on the data
    }
}
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task ProcessFileAsync(string filePath)
    {
        const int bufferSize = 4096;
        using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            byte[] buffer = new byte[bufferSize];
            while (true)
            {
                int bytesRead = await fileStream.ReadAsync(buffer.AsMemory());
                if (bytesRead == 0)
                    break;

                // Process the data using Span without unnecessary copying
                ProcessData(buffer.AsSpan(0, bytesRead));
            }
        }
    }

    static void ProcessData(Span<byte> data)
    {
        // Perform operations on the data
    }
}
Imports System
Imports System.IO
Imports System.Threading.Tasks

Friend Class Program
	Private Shared Async Function ProcessFileAsync(ByVal filePath As String) As Task
		Const bufferSize As Integer = 4096
		Using fileStream As New FileStream(filePath, FileMode.Open, FileAccess.Read)
			Dim buffer(bufferSize - 1) As Byte
			Do
				Dim bytesRead As Integer = Await fileStream.ReadAsync(buffer.AsMemory())
				If bytesRead = 0 Then
					Exit Do
				End If

				' Process the data using Span without unnecessary copying
				ProcessData(buffer.AsSpan(0, bytesRead))
			Loop
		End Using
	End Function

	Private Shared Sub ProcessData(ByVal data As Span(Of Byte))
		' Perform operations on the data
	End Sub
End Class
$vbLabelText   $csharpLabel

Aquí, el método ReadAsync lee datos desde un flujo de archivos hacia el buffer, y el método ProcessData procesa los datos directamente desde el Span.

3. Procesamiento de tareas asíncronas:

Al trabajar con tareas asincrónicas que producen o consumen datos, puedes usar Memory o Span para evitar copias innecesarias.

using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static async Task<int> ProcessDataAsync(int[] data)
    {
        // Asynchronous processing of data
        await Task.Delay(1000);
        // Returning the length of the processed data
        return data.Length;
    }

    static async Task Main()
    {
        int[] inputData = Enumerable.Range(1, 1000).ToArray();

        // Process the data asynchronously without copying
        int processedLength = await ProcessDataAsync(inputData.AsMemory());
        Console.WriteLine($"Processed data length: {processedLength}");
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static async Task<int> ProcessDataAsync(int[] data)
    {
        // Asynchronous processing of data
        await Task.Delay(1000);
        // Returning the length of the processed data
        return data.Length;
    }

    static async Task Main()
    {
        int[] inputData = Enumerable.Range(1, 1000).ToArray();

        // Process the data asynchronously without copying
        int processedLength = await ProcessDataAsync(inputData.AsMemory());
        Console.WriteLine($"Processed data length: {processedLength}");
    }
}
Imports System
Imports System.Linq
Imports System.Threading.Tasks

Friend Class Program
	Private Shared Async Function ProcessDataAsync(ByVal data() As Integer) As Task(Of Integer)
		' Asynchronous processing of data
		Await Task.Delay(1000)
		' Returning the length of the processed data
		Return data.Length
	End Function

	Shared Async Function Main() As Task
		Dim inputData() As Integer = Enumerable.Range(1, 1000).ToArray()

		' Process the data asynchronously without copying
		Dim processedLength As Integer = Await ProcessDataAsync(inputData.AsMemory())
		Console.WriteLine($"Processed data length: {processedLength}")
	End Function
End Class
$vbLabelText   $csharpLabel

En este ejemplo, el método ProcessDataAsync procesa los datos asíncronamente y devuelve la longitud de los datos procesados sin requerir copias adicionales.

Presentando IronPDF

Resumen de la biblioteca IronPDF es la última biblioteca PDF de C# de Iron Software que puede usarse para generar documentos PDF hermosos sobre la marcha de manera dinámica usando código C#. IronPDF proporciona una variedad de características como generación de PDF desde HTML, convertir contenido HTML a PDF, fusionar o dividir archivos PDF, etc.

La característica principal de IronPDF es su funcionalidad HTML a PDF, que preserva los diseños y estilos. Puede generar PDFs a partir de contenido web, lo que lo hace excelente para informes, facturas y documentación. Esta herramienta es compatible con la conversión de archivos HTML, URLs y cadenas HTML a archivos PDF.

using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
Imports IronPdf

Friend Class Program
	Shared Sub Main(ByVal args() As String)
		Dim renderer = New ChromePdfRenderer()

		' 1. Convert HTML String to PDF
		Dim htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>"
		Dim pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent)
		pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf")

		' 2. Convert HTML File to PDF
		Dim htmlFilePath = "path_to_your_html_file.html" ' Specify the path to your HTML file
		Dim pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath)
		pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf")

		' 3. Convert URL to PDF
		Dim url = "http://ironpdf.com" ' Specify the URL
		Dim pdfFromUrl = renderer.RenderUrlAsPdf(url)
		pdfFromUrl.SaveAs("URLToPDF.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

Instalación

IronPDF se puede instalar utilizando el administrador de paquetes NuGet para IronPDF en la consola o usando el administrador de paquetes de Visual Studio.

dotnet add package IronPdf
// Or
Install-Package IronPdf

C# Span (Cómo Funciona para los Desarrolladores): Figura 1 - Instalar IronPDF usando el Administrador de Paquetes NuGet buscando IronPDF en la barra de búsqueda del Administrador de Paquetes NuGet

using System;
using IronPdf;

class Program
{
    static void Main()
    {
        Console.WriteLine("Generating PDF using IronPDF.");
        var displayFirstName = "<p>First Name is Joe</p>".AsSpan();
        var displayLastName = "<p>Last Name is Doe</p>".AsSpan();
        var displayAddress = "<p>12th Main, 7Th Cross, New York</p>".AsSpan();
        var start = "<html><body>".AsSpan();
        var end = "</body></html>".AsSpan();
        var content = string.Concat(start.ToString(), displayFirstName.ToString(), displayLastName.ToString(), displayAddress.ToString(), end.ToString());
        var pdfDocument = new ChromePdfRenderer();
        pdfDocument.RenderHtmlAsPdf(content).SaveAs("span.pdf");
    }
}
using System;
using IronPdf;

class Program
{
    static void Main()
    {
        Console.WriteLine("Generating PDF using IronPDF.");
        var displayFirstName = "<p>First Name is Joe</p>".AsSpan();
        var displayLastName = "<p>Last Name is Doe</p>".AsSpan();
        var displayAddress = "<p>12th Main, 7Th Cross, New York</p>".AsSpan();
        var start = "<html><body>".AsSpan();
        var end = "</body></html>".AsSpan();
        var content = string.Concat(start.ToString(), displayFirstName.ToString(), displayLastName.ToString(), displayAddress.ToString(), end.ToString());
        var pdfDocument = new ChromePdfRenderer();
        pdfDocument.RenderHtmlAsPdf(content).SaveAs("span.pdf");
    }
}
Imports System
Imports IronPdf

Friend Class Program
	Shared Sub Main()
		Console.WriteLine("Generating PDF using IronPDF.")
		Dim displayFirstName = "<p>First Name is Joe</p>".AsSpan()
		Dim displayLastName = "<p>Last Name is Doe</p>".AsSpan()
		Dim displayAddress = "<p>12th Main, 7Th Cross, New York</p>".AsSpan()
		Dim start = "<html><body>".AsSpan()
		Dim [end] = "</body></html>".AsSpan()
		Dim content = String.Concat(start.ToString(), displayFirstName.ToString(), displayLastName.ToString(), displayAddress.ToString(), [end].ToString())
		Dim pdfDocument = New ChromePdfRenderer()
		pdfDocument.RenderHtmlAsPdf(content).SaveAs("span.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

En este ejemplo, estamos usando Span junto con IronPDF para generar un documento PDF.

Output:

C# Span (Cómo Funciona para los Desarrolladores): Figura 2 - Salida de Consola

PDF Generado:

C# Span (Cómo Funciona para los Desarrolladores): Figura 3 - Salida PDF

Licencia (Prueba gratuita disponible)

Información de la licencia de IronPDF. Esta clave debe colocarse en appsettings.json.

"IronPdf.LicenseKey": "your license key"

Proporcione su correo para una licencia de prueba.

Conclusión

Span en C# proporciona una forma poderosa y eficiente de trabajar con memoria, ofreciendo beneficios en términos de rendimiento y flexibilidad. Su naturaleza no propietaria y contigua lo hace particularmente adecuado para escenarios donde minimizar las asignaciones de memoria y las copias es crucial. Al aprovechar Span, los desarrolladores pueden lograr un mejor rendimiento en una variedad de aplicaciones, que van desde la manipulación de cadenas hasta el procesamiento numérico de alto rendimiento. Al comprender sus características y considerar sus limitaciones, los desarrolladores pueden aprovechar Span para diversas tareas de manipulación de memoria de manera segura y eficiente. Junto con resumen de la biblioteca IronPDF, puede usarse para generar documentos PDF impresionantes sin esperar y rendir límites.

Por favor, visita la página de Documentación de Inicio Rápido de IronPDF.

Preguntas Frecuentes

¿Qué es Span en C# y por qué es importante?

Span es un tipo introducido en C# 7.2 que representa una región contigua de memoria. Es importante porque permite a los desarrolladores realizar operaciones de memoria de bajo nivel de manera eficiente sin la sobrecarga de las asignaciones en el heap, manteniendo la seguridad y el rendimiento de C#.

¿Cómo optimiza Span la manipulación de memoria en C#?

Span optimiza la manipulación de memoria proporcionando una abstracción de cero copias sobre la memoria, permitiendo a los desarrolladores referenciar bloques de memoria existentes sin duplicar datos. Esto lleva a mejoras en el rendimiento, particularmente en aplicaciones que manejan grandes volúmenes de datos.

¿Cuál es la diferencia entre Span y ReadOnlySpan?

Span es una vista mutable de la memoria, permitiendo modificaciones, mientras que ReadOnlySpan proporciona una vista de solo lectura. ReadOnlySpan se utiliza cuando los datos no deben ser modificados, ofreciendo beneficios de rendimiento similares a la vez que asegura la integridad de los datos.

¿Puede Span ser utilizado con memoria no administrada en C#?

Sí, Span puede ser utilizado con memoria no administrada creando un span desde un puntero a memoria no administrada. Esto permite la manipulación directa de la memoria mientras se asegura que está correctamente asignada y liberada usando métodos como Marshal.AllocHGlobal y Marshal.FreeHGlobal.

¿Cómo se integra IronPDF con Span para la generación de PDF?

IronPDF puede trabajar junto a Span para generar PDFs dinámicamente al gestionar memoria de manera eficiente y evitar asignaciones innecesarias. Esta integración permite a los desarrolladores crear documentos PDF a partir de contenido web con un rendimiento mejorado.

¿Cuáles son las limitaciones del uso de Span para la gestión de memoria?

Las limitaciones del uso de Span incluyen el requisito de memoria contigua, la falta de gestión automática de memoria y recolección de basura, y la no compatibilidad con memoria no contigua. Los desarrolladores deben garantizar manualmente la validez y los límites de la memoria.

¿Cómo se puede instalar IronPDF para la manipulación de PDF en C#?

IronPDF se puede instalar en un proyecto C# usando el gestor de paquetes NuGet. Usa comandos como dotnet add package IronPdf o Install-Package IronPdf para añadirlo a tu proyecto.

¿Cuáles son los beneficios de usar Span para la manipulación de cadenas?

Span permite una manipulación eficiente de cadenas al minimizar las asignaciones de memoria y la copia. Esto es particularmente beneficioso en código crítico para el rendimiento donde se procesa una gran cantidad de datos de cadenas.

¿Hay una versión de prueba disponible para IronPDF?

Sí, IronPDF ofrece una licencia de prueba que se puede obtener proporcionando tu correo electrónico. La clave de la licencia de prueba debe colocarse en el archivo appsettings.json para usar la biblioteca.

¿Puede Span ser utilizado en llamadas de método asíncronas?

Sí, Span puede ser utilizado en llamadas de método asíncronas para manejar datos de manera eficiente sin copias innecesarias. Esto es particularmente útil en operaciones de E/S y procesamiento de archivos, aprovechando Memory o Span.

Jacob Mellor, Director de Tecnología @ Team Iron
Director de Tecnología

Jacob Mellor es Director de Tecnología en Iron Software y un ingeniero visionario que lidera la tecnología PDF en C#. Como el desarrollador original detrás de la base de código central de Iron Software, ha moldeado la arquitectura de productos de la compañía desde ...

Leer más