跳至页脚内容
.NET 帮助

C# Span(开发人员如何使用)

Span 是一种在 C# 7.2 中引入的类型,作为 System 命名空间中 Span 结构的一部分。 它旨在表示任意内存的连续区域。 与数组或集合(如托管堆)不同,Span 不拥有其指向的堆栈内存或内存区域; 相反,它提供了对现有内存块的轻量级视图。 这种特性使得 Span 对于需要有效地处理内存缓冲而不增加额外开销和不安全代码场景的情况尤其强大。 Later in this article, we shall also see the introduction to the IronPDF library from Iron Software.

Span 的关键特性

1. 内存管理

C# 中的 Span 允许开发人员直接处理内存,而无需借助传统的堆分配。 它提供了一种从现有数组或其他内存来源创建内存切片的方法,消除了额外的内存拷贝需求。

2. 零拷贝抽象

C# Span 的一个突出功能是其零拷贝抽象。 Span 提供了一种高效引用现有内存的方法,而不是复制数据。 这对需要处理大量数据且复制成本过高或不切实际的场景尤其有益。

3. 类指针操作

虽然 C# 传统上是高级且安全的语言,但 Span 引入了一些类似于在 C 或 C++ 等语言中处理指针的低级内存操作能力。 开发人员可以执行类指针操作而不牺牲 C# 的安全性和托管特性。

4. 不可变特性

尽管具有低级内存访问能力,C# Span 仍然是不变的。 这意味着,尽管它允许操作内存,但通过防止意外修改来确保安全性。

示例

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

ReadOnlySpan

虽然 Span 是可变的并允许对底层数据进行修改,但 ReadOnlySpan 是内存的不可变视图。 它为一块连续内存区域提供了只读接口,使其适合仅需读取数据而不修改数据的场景。

这里有几个要点。

1. 只读视图

顾名思义,ReadOnlySpan 允许您创建一块内存的只读视图。 这意味着你不能通过 ReadOnlySpan 修改元素。

2. 内存表示

与 Span 类似,ReadOnlySpan 不拥有其指向的内存。 它引用现有内存,可以指向数组、堆栈分配的内存或本机内存。

3. 性能优势

与 Span 类似,ReadOnlySpan 相较传统集合类型可以提供更好的性能,尤其是在处理大量数据时,因为它减少了拷贝的需求。

4. 无边界检查

与 Span 一样,ReadOnlySpan 不进行边界检查。 开发人员有责任确保操作保持在底层内存的范围内。

5. 与数组切片的使用

ReadOnlySpan 支持切片,使您能够创建引用原始内存一部分的子 Span。

示例

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

有多种不同方法可以创建和使用 ReadOnlySpan。 以下是一些示例。

1. 从字符串创建 ReadOnlySpan

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. 处理子字符串

在 ReadOnlySpan 上使用 Slice

// 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. 将子字符串传递给方法

将 ReadOnlySpan 作为参数传递给方法。

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. 在字符串中搜索

ReadOnlySpan 用于在字符串中使用 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. 使用内存映射文件

ReadOnlySpan 在内存映射文件中表现得更加高效。

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. 高效的字符串操作

ReadOnlySpan 可用于高效的字符串操作。

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. 将子字符串传递给 API

在与操作字符 Span 的外部库或 API 一起使用时。

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 提供了一种更高效处理字符串的方法,特别在需要最大限度减少内存分配和拷贝的情况下。 这是一种优化性能关键代码的强大工具,在处理大量字符串数据时尤其有益。

Span 限制

虽然 C# 中的 Span 是一种强大的特性,具备众多优势,但在连续和非连续内存的背景下,的确有其某些限制和考虑。 让我们探讨这些限制:

1. 连续内存缓冲区

1.1 无自动内存管理

Span 不管理其指向的内存。 这意味着如果底层托管内存被释放或超出范围,使用 Span 将导致未定义的行为或潜在的崩溃。 开发人员需要确保在使用 Span 时底层内存仍然有效。

1.2 无垃圾收集

由于 Span 不拥有内存,因此不受垃圾回收的影响。 因此,在处理堆栈分配的内存或比 Span 本身生命周期更短的内存时需谨慎。

1.3 边界检查被禁用

Span 和 ReadOnlySpan 默认不执行边界检查。 这可能导致如果使用不当,访问无效的内存位置。 开发人员需要手动确保对 Span 的操作保持在底层内存的范围内。

1.4 不支持非连续内存

Span 设计用于处理连续内存。 如果存有非连续内存或需要表示更复杂的数据结构,Span 可能不是最合适的选择。

1.5 不是所有操作都支持

虽然 Span 支持许多常见操作,如切片、索引、和迭代,但并非所有操作都受支持。 例如,您不能调整 Span 的大小,以及某些涉及改变底层内存长度的操作是不允许的。

1.6 限制的平台兼容性

虽然 Span 是 .NET Standard 和 .NET Core 的一部分,但可能无法在所有平台或环境中使用。 如果计划在代码中使用 Span,确保目标平台支持它至关重要。

2. 非连续内存缓冲区

2.1 非连续内存支持有限

ReadOnlySpan 主要是为与连续内存块或缓冲区无缝协作而设计的。 对于涉及非连续内存缓冲区或有内存间隙的结构的场景,它可能不是最佳选择。

2.2 结构限制

某些依赖于非连续内存的数据结构或场景可能与 ReadOnlySpan 不太匹配。 例如,像链表或图结构这样的数据结构可能不太适合,因为 ReadOnlySpan 的连续内存要求。

2.3 复杂的指针操作

在需要复杂指针运算的非连续内存情况下,ReadOnlySpan 可能无法提供与 C++ 等语言的原始指针相同的低级控制和灵活性。 在这种情况下,使用不安全代码与指针可能更合适。

2.4 某些 API 缺乏直接支持

与连续内存类似,重要的是要注意,并非所有 API 或库都可能直接支持由 ReadOnlySpan 表示的非连续内存。 适应此类场景可能需要额外的中间步骤或转换以确保兼容性。

Span 和非托管内存

在 C# 中,Span 可以有效地与非托管内存结合使用,以进行受控和高效的内存相关操作。 非托管内存是指不由 .NET 运行时的垃圾收集器管理的内存,通常涉及使用本机内存分配和释放。 以下是 Span 在 C# 中与非托管内存结合使用的方法。

分配非托管内存

要分配非托管内存,可以使用 System.Runtime.InteropServices.MemoryMarshal 类。 Marshal.AllocHGlobal 方法分配内存并返回指向已分配块的指针。 已分配的内存或内存地址存储在 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);
    }
}
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

在以上源码中,我们使用 Marshal.AllocHGlobal 分配了一块非托管内存,然后使用从非托管内存获取的指针创建了一个 Span<byte>。 这允许我们使用熟悉的 Span API 与非托管内存交互。 需要注意的是,在处理非托管内存时,您有责任管理内存的分配和释放。

复制数据到及从非托管内存

Span 提供了 SliceCopyToToArray 方法,可以用于有效地在托管和非托管内存之间复制数据。

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

在此示例中:

  • Marshal.AllocHGlobal 为目标数据分配非托管内存。
  • new Span<int>(destinationPointer.ToPointer(), sourceArray.Length) 从分配的非托管内存创建一个 Span<int>
  • sourceSpan.CopyTo(destinationSpan) 方法将数据从托管数组复制到非托管内存。
  • 通过打印目标内存中的值验证复制操作。
  • 方法 Marshal.FreeHGlobal(destinationPointer) 在完成操作时用于释放非托管内存。

使用不安全代码

在处理非托管内存时,您也可以使用带有指针的不安全代码。 在这种情况下,可以使用 Unsafe.AsPointer() 方法从 Span 获取指针。

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

在此示例中,我们使用 Unsafe.AsPointer 方法从 Span 获取指针。 这允许我们在直接处理指针时使用不安全代码。

请记住,在处理非托管内存时,正确管理内存的分配和释放以避免内存泄漏至关重要。 始终使用适当的方法(如 Marshal.FreeHGlobal())释放非托管内存。 此外,使用不安全代码时务必小心,因为如果处理不当可能会引入潜在的安全风险。

Span 和异步方法调用

在 C# 中将 Span 与异步方法调用结合使用是一种强大的组合,特别是在处理大量数据或 I/O 操作时。 目标是不进行不必要的数据拷贝高效处理异步操作。 让我们探讨如何在异步场景中利用 Span:

1. 异步 I/O 操作:

在处理异步 I/O 操作时,例如读取或写入数据流,可以使用 Memory 或 Span 来高效地处理数据而不创建额外的缓冲区。

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

在此示例中,ReadAsync 方法异步从流中读取数据到缓冲区。 然后 ProcessData 方法直接从 Span 中处理数据而不将其复制到另一个缓冲区。

2. 异步文件操作:

与 I/O 操作类似,在处理异步文件操作时,可以使用 Span 来高效处理数据,而无需额外拷贝。

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

此处,ReadAsync 方法从文件流中读取数据到缓冲区,然后 ProcessData 方法直接从 Span 中处理数据。

3. 异步任务处理:

在处理生成或消耗数据的异步任务时,可以使用 Memory 或 Span 以避免不必要的复制。

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

在此示例中,ProcessDataAsync 方法异步处理数据并返回处理数据的长度而不需要额外的副本。

IronPDF 简介

IronPDF library overview is the latest C# PDF library from Iron Software 的最新 C# PDF 库,可以使用 C# 代码动态生成精美的 PDF 文档。 IronPDF 提供了多种功能,例如从 HTML 生成 PDF、将 HTML 内容转换为 PDF、合并或拆分 PDF 文件等。

IronPDF 的主要功能是其 HTML 到 PDF 功能,可以保留布局和样式。 它可以从网页内容生成 PDF,让它非常适合报告、发票和文档。 此工具支持将 HTML 文件、URL 和 HTML 字符串转换为 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

安装

IronPDF 可以通过 NuGet 包管理器用于 IronPDF 控制台或使用 Visual Studio 包管理器安装。

dotnet add package IronPdf
// Or
Install-Package IronPdf

C# Span(如何适合开发人员):图 1 - 通过在 NuGet 包管理器的搜索栏中搜索“ironpdf”来使用 NuGet 包管理器安装 IronPDF

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

在此示例中,我们使用 Span 结合 IronPDF 生成 PDF 文档。

输出:

C# Span(如何适合开发人员):图 2 - 控制台输出

生成的 PDF:

C# Span(如何适合开发人员):图 3 - PDF 输出

许可(提供免费试用)

IronPDF 许可证信息。 该密钥需要放在appsettings.json中。

"IronPdf.LicenseKey": "your license key"

提供您的电子邮件以获取试用许可证。

结论

C# 中的 Span 提供了一种强大且高效处理内存的方法,在性能和灵活性方面提供了优势。 其不持有对象的连续特性让其特别适合于需要将内存分配和拷贝最小化的场景。 通过利用 Span,开发人员可以在各种应用程序中获得更好的性能,从字符串操作到高性能数字处理。 通过理解其特性并考虑其限制,开发人员可以安全高效地使用 Span 执行各种内存操作任务。 与 IronPDF 库概述 相结合,可在不需要“等待”和“生成”边界的情况下生成出色的 PDF 文档。

请访问 IronPDF 的快速入门文档页面 页面。

常见问题解答

C# 中的 Span 是什么?为什么它很重要?

Span 是 C# 7.2 引入的一种类型,表示一块连续的内存区域。它之所以重要,是因为它允许开发人员在不影响 C# 安全性和性能的情况下,高效地执行低级内存操作,无需堆分配的开销。

Span 如何优化 C# 中的内存操作?

Span 通过提供对内存的零复制抽象来优化内存操作,使开发人员能够引用现有的内存块而不复制数据。这特别在处理大数据量的应用程序中提升了性能。

Span 与 ReadOnlySpan 的区别是什么?

Span 是可变的内存视图,允许修改,而 ReadOnlySpan 提供的是只读视图。ReadOnlySpan 在数据不应修改时使用,既提供类似的性能优势,又确保数据的完整性。

Span 可以在 C# 中与非托管内存一起使用吗?

是的,Span 可以通过从指向非托管内存的指针创建 span 来与非托管内存一起使用。这允许直接操作内存,同时确保通过 Marshal.AllocHGlobalMarshal.FreeHGlobal 这样的方法正确地分配和释放内存。

IronPDF 如何与 Span 集成以生成 PDF?

IronPDF 可以与 Span 一起工作,通过高效管理内存和避免不必要的分配来动态生成 PDF。此集成使开发人员能够以更好的性能从网页内容创建 PDF 文档。

使用 Span 进行内存管理有哪些限制?

使用 Span 的限制包括需要连续内存、没有自动内存管理和垃圾回收,以及不支持非连续内存。开发人员必须手动确保内存的有效性和边界。

如何安装 IronPDF 以便在 C# 中进行 PDF 操作?

可以通过 NuGet 包管理器在 C# 项目中安装 IronPDF。使用诸如 dotnet add package IronPdfInstall-Package IronPdf 的命令将其添加到您的项目中。

使用 Span 进行字符串操作有哪些好处?

Span 通过最小化内存分配和复制来实现高效的字符串操作。此特性在需要处理大量字符串数据的性能关键代码中尤其有益。

IronPDF 是否提供试用版本?

是的,IronPDF 提供试用许可证,可以通过提供您的电子邮件获取。试用许可证密钥应放置在 appsettings.json 文件中以使用该库。

Span 可以用于异步方法调用吗?

是的,Span 可以用于异步方法调用,以便无不必要复制地高效处理数据。这在 I/O 操作和文件处理中特别有用,可利用 MemorySpan

Curtis Chau
技术作家

Curtis Chau 拥有卡尔顿大学的计算机科学学士学位,专注于前端开发,精通 Node.js、TypeScript、JavaScript 和 React。他热衷于打造直观且美观的用户界面,喜欢使用现代框架并创建结构良好、视觉吸引力强的手册。

除了开发之外,Curtis 对物联网 (IoT) 有浓厚的兴趣,探索将硬件和软件集成的新方法。在空闲时间,他喜欢玩游戏和构建 Discord 机器人,将他对技术的热爱与创造力相结合。