Published March 7, 2024
C# Span (How It Works For Developers)
Span is a type introduced in C# 7.2 as part of the Span
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
ReadOnlySpan
While Span
Here are some key points.
1. Read-Only View
As the name suggests, ReadOnlySpan
2. Memory Representation
Like Span
3. Performance Benefits
Like Span
4. No Bounds Checking
As with Span
5. Usage with Array Slicing
ReadOnlySpan
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
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
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)
3. Passing Substring to a Method
Pass ReadOnlySpan
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))
4. Searching within a String
ReadOnlySpan
int index = stringSpan.IndexOf('W');
int index = stringSpan.IndexOf('W');
Dim index As Integer = stringSpan.IndexOf("W"c)
5. Using Memory-Mapped Files
ReadOnlySpan
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
6. Efficient String Manipulation
ReadOnlySpan
// 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)
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))
ReadOnlySpan
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
Span
1.2 No Garbage Collection
Since Span
1.3 Bounds Checking is Disabled
Span
1.4 No Support for Non-Contiguous Memory
Span
1.5 Not All Operations are Supported
While Span
1.6 Limited Platform Compatibility
While Span
2. Non-Contiguous Memory Buffers
2.1 Limited Support for Non-Contiguous Memory
ReadOnlySpan
2.2 Structural Limitations
Certain data structures or scenarios that rely on non-contiguous memory may not align well with ReadOnlySpan
2.3 Complex Pointer Operations
In situations involving non-contiguous memory, particularly those requiring intricate pointer arithmetic, ReadOnlySpan
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
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
In the above source code, we allocate a block of unmanaged memory using Marshal.AllocHGlobal and then create a Span
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
In this example:
MemoryMarshal.Allocate
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
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 Memory
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
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 Span
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
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 Memory
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
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
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
In this example, we are using Span along with IronPDF to generate a PDF document.
Output:
Generated PDF:
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"
Provide your email to get a trial license.
Conclusion
Span
Along with the free trial to for long-term use. To know more about how to use IronPDF please visit their documentation page.