C# Span (How It Works For Developers)

Span is a type introduced in C# 7.2 as part of the Spanstruct in the System namespace. It is designed to represent a contiguous region of arbitrary memory. Unlike arrays or collections such as managed heap, Span does not own the stack memory or region of memory it points to; instead, it provides a lightweight view over existing memory blocks. This characteristic makes Span particularly powerful for scenarios where you need to work with memory buffers efficiently without incurring additional overhead and unsafe code scenarios. Later in this article, we shall also see the introduction to IronPDF library from Iron Software.

Key Characteristics of Span

1. Memory Management

Span in C# allows developers to work with memory directly without resorting to traditional heap allocations. It offers a way to create slices of memory from existing arrays or other memory sources, eliminating the need for additional memory copies.

2. Zero-Copy Abstractions

One of the standout features of C# Span is its zero-copy abstractions. Instead of duplicating data, Span provides a way to reference existing memory efficiently. This is particularly beneficial for scenarios where copying large amounts of data would be impractical or too costly.

3. Pointer-Like Operations

While C# has traditionally been a high-level, safe language, Span introduces a degree of low-level memory manipulation akin to working with pointers in languages like C or C++. Developers can perform pointer-like operations without sacrificing the safety and managed nature of C#.

4. Immutable Nature

Despite its capabilities for low-level memory access, C# Span remains immutable. This means that, while it allows manipulation of memory, it enforces safety by preventing unintended modifications.

Example

using System;
class Program
{
    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
{
    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
	Private 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
VB   C#

ReadOnlySpan

While Spanis mutable and allows for modifications to the underlying data, ReadOnlySpanis an immutable view of memory. It provides a read-only interface to a contiguous region of memory, making it suitable for scenarios where you only need to read the data without modifying it.

Here are some key points.

1. Read-Only View

As the name suggests, ReadOnlySpanallows you to create a read-only view of a block of memory. This means you cannot modify the elements through a ReadOnlySpan.

2. Memory Representation

Like Span, ReadOnlySpandoes not own the memory it points to. It refers to existing memory and can point to arrays, stack-allocated memory, or native memory.

3. Performance Benefits

Like Span, ReadOnlySpancan offer better performance compared to traditional collection types, especially when dealing with large amounts of data, as it reduces the need for copying.

4. No Bounds Checking

As with Span, ReadOnlySpandoes not perform bounds checking. It is the developer's responsibility to ensure that operations stay within the bounds of the underlying memory.

5. Usage with Array Slicing

ReadOnlySpansupports slicing, allowing you to create sub-spans that reference a portion of the original memory.

Example

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
VB   C#

There are many different ways to create ReadOnlySpan and work with it. Below are some examples.

1. Creating ReadOnlySpan from 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
VB   C#

2. Working with Substrings

Use Slice on the ReadOnlySpan

ReadOnlySpan<char> substringSpan = spanFromString.Slice(startIndex, length);
ReadOnlySpan<char> substringSpan = spanFromString.Slice(startIndex, length);
Dim substringSpan As ReadOnlySpan(Of Char) = spanFromString.Slice(startIndex, length)
VB   C#

3. Passing Substring to a Method

Pass ReadOnlySpanas a parameter to the method.

void ProcessSubstringfromReadOnlySpan(ReadOnlySpan<char> substring)
{
    // Perform operations on the substring
}
// Usage
ProcessSubstringfromReadOnlySpan(spanFromString.Slice(startIndex, length));
void ProcessSubstringfromReadOnlySpan(ReadOnlySpan<char> substring)
{
    // Perform operations on the substring
}
// Usage
ProcessSubstringfromReadOnlySpan(spanFromString.Slice(startIndex, length));
Private Sub ProcessSubstringfromReadOnlySpan(ByVal substring As ReadOnlySpan(Of Char))
	' Perform operations on the substring
End Sub
' Usage
ProcessSubstringfromReadOnlySpan(spanFromString.Slice(startIndex, length))
VB   C#

4. Searching within a String

ReadOnlySpanfor searching within a string with IndexOf().

int index = stringSpan.IndexOf('W');
int index = stringSpan.IndexOf('W');
Dim index As Integer = stringSpan.IndexOf("W"c)
VB   C#

5. Using Memory-Mapped Files

ReadOnlySpancan be more efficient with memory-mapped files.

using (var memmf = MemoryMappedFile.CreateFromFile("data.bin"))
{
    using (var accessor = memmf.CreateViewAccessor())
    {
        ReadOnlySpan<byte> dataSpan;
        accessor.Read(0, out dataSpan);
        // Process data directly from the memory-mapped file
        ProcessData(dataSpan);
    }
}
using (var memmf = MemoryMappedFile.CreateFromFile("data.bin"))
{
    using (var accessor = memmf.CreateViewAccessor())
    {
        ReadOnlySpan<byte> dataSpan;
        accessor.Read(0, out dataSpan);
        // Process data directly from the memory-mapped file
        ProcessData(dataSpan);
    }
}
Using memmf = MemoryMappedFile.CreateFromFile("data.bin")
	Using accessor = memmf.CreateViewAccessor()
		Dim dataSpan As ReadOnlySpan(Of Byte) = Nothing
		accessor.Read(0, dataSpan)
		' Process data directly from the memory-mapped file
		ProcessData(dataSpan)
	End Using
End Using
VB   C#

6. Efficient String Manipulation

ReadOnlySpancan be used for efficient string manipulation.

// Replace a character in a substring without creating a new string
spanFromString.Slice(startIndex, length).CopyTo(newSpan);
// Replace a character in a substring without creating a new string
spanFromString.Slice(startIndex, length).CopyTo(newSpan);
' Replace a character in a substring without creating a new string
spanFromString.Slice(startIndex, length).CopyTo(newSpan)
VB   C#

7. Passing Substring to APIs

When working with external libraries or APIs that operate on character spans.

void ExternalApiMethod(ReadOnlySpan<char> data)
{
    // Call the external API with the character span
}
// Usage
ExternalApiMethod(spanFromString.Slice(startIndex, length));
void ExternalApiMethod(ReadOnlySpan<char> data)
{
    // Call the external API with the character span
}
// Usage
ExternalApiMethod(spanFromString.Slice(startIndex, length));
Private Sub ExternalApiMethod(ByVal data As ReadOnlySpan(Of Char))
	' Call the external API with the character span
End Sub
' Usage
ExternalApiMethod(spanFromString.Slice(startIndex, length))
VB   C#

ReadOnlySpanprovides a way to work with strings more efficiently, especially in scenarios where memory allocations and copying should be minimized. It's a powerful tool for optimizing performance-critical code and can be particularly beneficial when dealing with large amounts of string data.

Span Limitations

While Span in C# is a powerful feature with numerous advantages, it does come with certain limitations and considerations, particularly in the context of contiguous and non-contiguous memory. Let's explore these limitations:

1. Contiguous Memory Buffers

1.1 No Automatic Memory Management

Spandoes not manage the memory it points to. This means that if the underlying managed memory is released or goes out of scope, using the Spanwill result in undefined behavior or potential crashes. Developers need to ensure that the underlying memory is still valid when using a Span.

1.2 No Garbage Collection

Since Spandoes not own memory, it is not subject to garbage collection. Therefore, you need to be careful when working with stack-allocated memory or memory that has a shorter lifetime than the Spanitself.

1.3 Bounds Checking is Disabled

Spanand ReadOnlySpando not perform bounds checking by default. This can lead to accessing invalid memory locations if not used carefully. Developers need to manually ensure that the operations on a Spanstay within the bounds of the underlying memory.

1.4 No Support for Non-Contiguous Memory

Spanis designed to work with contiguous memory. If you have non-contiguous memory or need to represent more complex data structures, Spanmay not be the most appropriate choice.

1.5 Not All Operations are Supported

While Spansupports many common operations like slicing, indexing, and iterating, not all operations are supported. For example, you cannot resize a Span, and certain operations that involve changing the length of the underlying memory are not allowed.

1.6 Limited Platform Compatibility

While Spanis part of the .NET Standard and .NET Core, it may not be available in all platforms or environments. It is crucial to ensure that your target platform supports Spanif you plan to use it in your code.

2. Non-Contiguous Memory Buffers

2.1 Limited Support for Non-Contiguous Memory

ReadOnlySpanis primarily designed to work seamlessly with contiguous memory blocks or buffers. It may not be the most suitable choice for scenarios where non-contiguous memory buffers or structures with gaps in memory are involved.

2.2 Structural Limitations

Certain data structures or scenarios that rely on non-contiguous memory may not align well with ReadOnlySpan. For instance, data structures like linked lists or graph structures might not be well-suited due to the contiguous memory requirement of ReadOnlySpan.

2.3 Complex Pointer Operations

In situations involving non-contiguous memory, particularly those requiring intricate pointer arithmetic, ReadOnlySpanmight not offer the same low-level control and flexibility as raw pointers in languages like C++. In such cases, utilizing unsafe code with pointers could be more appropriate.

2.4 Lack of Direct Support in Some APIs

Similar to contiguous memory, it's important to note that not all APIs or libraries may directly support non-contiguous memory represented by ReadOnlySpan. Adapting to such scenarios might necessitate additional intermediate steps or conversions to ensure compatibility.

Span and Unmanaged Memory

In C#, Span can be effectively used with unmanaged memory to perform memory-related operations in a controlled and efficient manner. Unmanaged memory refers to memory that is not managed by the .NET runtime's garbage collector, and it often involves using native memory allocations and deallocations. Here's how Span can be utilized with unmanaged memory in C#.

Allocating Unmanaged Memory

To allocate unmanaged memory, you can use the System.Runtime.InteropServices.MemoryMarshal class. The Marshal.AllocHGlobal method allocates memory and returns a pointer to the allocated block. The memory allocated or memory address is held in an unmanagedMemory pointer and will have read-write access. The contiguous regions of memory can be easily accessed.

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
VB   C#

In the above source code, we allocate a block of unmanaged memory using Marshal.AllocHGlobal and then create a Spanusing the pointer obtained from the unmanaged memory. This allows us to work with unmanaged memory using the familiar Span API. It's important to note that when working with unmanaged memory, you are responsible for managing the allocation and deallocation of the memory.

Copying Data to and from Unmanaged Memory

Span provides methods like Slice, CopyTo, and ToArray that can be used for copying data between managed and unmanaged memory efficiently.

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 = MemoryMarshal.Allocate<int>(sourceArray.Length);
        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 = MemoryMarshal.Cast<int, byte>(destinationPointer, 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
            MemoryMarshal.Free(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 = MemoryMarshal.Allocate<int>(sourceArray.Length);
        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 = MemoryMarshal.Cast<int, byte>(destinationPointer, 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
            MemoryMarshal.Free(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 = MemoryMarshal.Allocate(Of Integer)(sourceArray.Length)
		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 Span(Of Integer) = MemoryMarshal.Cast(Of Integer, Byte)(destinationPointer, 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
			MemoryMarshal.Free(destinationPointer)
		End Try
	End Sub
End Class
VB   C#

In this example:

MemoryMarshal.Allocate(sourceArray.Length) allocates unmanaged memory for the destination data. MemoryMarshal.Cast<int, byte>(destinationPointer, sourceArray.Length) creates a Spanfrom the allocated unmanaged memory. The sourceSpan.CopyTo(destinationSpan) method copies the data from the managed array to the unmanaged memory. The values in the destination memory are printed to verify the copy operation. The MemoryMarshal.Free(destinationPointer) method is used to deallocate the unmanaged memory when done.

Using Unsafe Code

When dealing with unmanaged memory, you may also use unsafe code with pointers. In such cases, you can obtain a pointer from the Span using the Unsafe.AsPointer() method.

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 unsafe code to work with pointers
        // ref t
        unsafe
        {
            byte* pointer = (byte*)Unsafe.AsPointer(ref struct 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;
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
        // ref t
        unsafe
        {
            byte* pointer = (byte*)Unsafe.AsPointer(ref struct 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
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
		' ref t
'INSTANT VB TODO TASK: C# 'unsafe' code is not converted by Instant VB:
'		unsafe
'		{
'			byte* pointer = (byte*)Unsafe.AsPointer(ref struct 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
VB   C#

In this example, we use the Unsafe.AsPointer method to obtain a pointer from the Span. This allows us to use unsafe code when working with pointers directly.

Remember, when working with unmanaged memory, it's crucial to manage the allocation and deallocation properly to avoid memory leaks. Always free unmanaged memory using appropriate methods, such as Marshal.FreeHGlobal(). Additionally, exercise caution when using unsafe code, as it can introduce potential security risks if not handled properly.

Span and Asynchronous Method Calls

Using Span in conjunction with asynchronous method calls in C# is a powerful combination, especially when dealing with large amounts of data or I/O operations. The goal is to handle asynchronous operations without unnecessary copying of data efficiently. Let's explore how you can leverage Span in asynchronous scenarios:

1. Asynchronous I/O Operations:

When dealing with asynchronous I/O operations, such as reading or writing data to a stream, you can use Memoryor Spanto efficiently work with the data without creating additional buffers.

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));
    }
}
void ProcessData(Span<byte> data)
{
    // Perform operations on the data
}
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));
    }
}
void ProcessData(Span<byte> data)
{
    // Perform operations on the data
}
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 Sub ProcessData(ByVal data As Span(Of Byte))
	' Perform operations on the data
End Sub
VB   C#

In this example, the ReadAsync method asynchronously reads data from a stream into the buffer. The ProcessData method then processes the data directly from the Spanwithout copying it to another buffer.

2. Asynchronous File Operations:

Similar to I/O operations, when dealing with asynchronous file operations, you can use Span to efficiently process data without additional copying.

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));
        }
    }
}
void ProcessData(Span<byte> data)
{
    // Perform operations on the data
}
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));
        }
    }
}
void ProcessData(Span<byte> data)
{
    // Perform operations on the data
}
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 Sub ProcessData(ByVal data As Span(Of Byte))
	' Perform operations on the data
End Sub
VB   C#

Here, the ReadAsync method reads data from a file stream into the buffer, and the ProcessData method processes the data directly from the Span.

3. Asynchronous Task Processing:

When working with asynchronous tasks that produce or consume data, you can use Memoryor Spanto avoid unnecessary copying.

async Task<int> ProcessDataAsync(int[] data)
{
    // Asynchronous processing of data
    await Task.Delay(1000);
    // Returning the length of the processed data
    return data.Length;
}
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}");
}
async Task<int> ProcessDataAsync(int[] data)
{
    // Asynchronous processing of data
    await Task.Delay(1000);
    // Returning the length of the processed data
    return data.Length;
}
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}");
}
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
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
VB   C#

In this example, the ProcessDataAsync method processes the data asynchronously and returns the length of the processed data without requiring additional copies.

Introducing IronPDF

IronPDF is the latest C# PDF library from Iron Software which can be used to generate beautiful PDF documents on the fly dynamically using C# code. IronPDF provides a variety of features such as PDF generation from HTML, converting HTML content to PDF, merging or splitting PDF files, etc.

Installation

IronPDF can be installed using the NuGet Package Manager console or using the Visual Studio package manager.

dotnet add package IronPdf
// Or
Install-Package IronPdf
dotnet add package IronPdf
// Or
Install-Package IronPdf
'INSTANT VB TODO TASK: The following line uses invalid syntax:
'dotnet add package IronPdf Install-Package IronPdf
VB   C#

C# Span (How It Works For Developers): Figure 1 - Install IronPDF using NuGet Package Manager by searching "ironpdf" in the search bar of NuGet Package Manager

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Generating PDF using IronPDF.");
        var displayFirstName = "<p>First Name is Joe</p>".AsSpan();
        var displayLastName = "<p>First Name is Doe</p>".AsSpan();
        var displayAddress = "<p>12th Main, 7Th Cross, New York</p>".AsSpan();
        var start = @"<!DOCTYPE html>
<html>
<body>".AsSpan();
        var end = @"<!DOCTYPE html>
<html>
<body>";
        var content = string.Concat(start, displayFirstName, displayLastName, string.Concat(displayAddress, end));
        var pdfDocument = new ChromePdfRenderer();
        pdfDocument.RenderHtmlAsPdf(content).SaveAs("span.pdf");
    }
}
using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Generating PDF using IronPDF.");
        var displayFirstName = "<p>First Name is Joe</p>".AsSpan();
        var displayLastName = "<p>First Name is Doe</p>".AsSpan();
        var displayAddress = "<p>12th Main, 7Th Cross, New York</p>".AsSpan();
        var start = @"<!DOCTYPE html>
<html>
<body>".AsSpan();
        var end = @"<!DOCTYPE html>
<html>
<body>";
        var content = string.Concat(start, displayFirstName, displayLastName, string.Concat(displayAddress, end));
        var pdfDocument = new ChromePdfRenderer();
        pdfDocument.RenderHtmlAsPdf(content).SaveAs("span.pdf");
    }
}
Imports System
Friend Class Program
	Shared Sub Main()
		Console.WriteLine("Generating PDF using IronPDF.")
		Dim displayFirstName = "<p>First Name is Joe</p>".AsSpan()
		Dim displayLastName = "<p>First Name is Doe</p>".AsSpan()
		Dim displayAddress = "<p>12th Main, 7Th Cross, New York</p>".AsSpan()
		Dim start = "<!DOCTYPE html>
<html>
<body>".AsSpan()
		Dim [end] = "<!DOCTYPE html>
<html>
<body>"
		Dim content = String.Concat(start, displayFirstName, displayLastName, String.Concat(displayAddress, [end]))
		Dim pdfDocument = New ChromePdfRenderer()
		pdfDocument.RenderHtmlAsPdf(content).SaveAs("span.pdf")
	End Sub
End Class
VB   C#

In this example, we are using Span along with IronPDF to generate a PDF document.

Output:

C# Span (How It Works For Developers): Figure 2 - Console Output

Generated PDF:

C# Span (How It Works For Developers): Figure 3 - PDF Output

Licensing (Free Trial Available)

IronPDF. This key needs to be placed in appsettings.json.

"IronPdf.LicenseKey": "your license key"
"IronPdf.LicenseKey": "your license key"
'INSTANT VB TODO TASK: The following line uses invalid syntax:
'"IronPdf.LicenseKey": "your license key"
VB   C#

Provide your email to get a trial license.

Conclusion

Spanin C# provides a powerful and efficient way to work with memory, offering benefits in terms of performance and flexibility. Its non-owning, contiguous nature makes it particularly suitable for scenarios where minimizing memory allocations and copying is crucial. By leveraging Span, developers can achieve better performance in a variety of applications, ranging from string manipulation to high-performance numeric processing. By understanding its features and considering its limitations, developers can leverage Spanfor various memory manipulation tasks safely and efficiently. Along with IronPDF, it can be used to generate awesome PDF documents without await and yield boundaries.

Along with the free trial to for long-term use. To know more about how to use IronPDF please visit their documentation page.