跳過到頁腳內容
.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. 性能優勢

與傳統集合類型相比,ReadOnlySpan 與 Span 一樣可以提供更好的性能,尤其是在處理大量數據時,因為它減少了對拷貝的需求。

4. 不進行邊界檢查

和 Span一樣,ReadOnlySpan 也不會執行邊界檢查。 開發者有責任確保操作在底層內存的範圍內。

5. 使用數組切片

ReadOnlySpan 支持切片,允許創建引用原始內存一部分的子視圖。

示例

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

當處理操作字元範圍的外部函式庫或 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 运行时垃圾收集器管理的內存,通常涉及使用本機內存分配和釋放。 以下是如何在 C# 中利用 Span 與非托管內存結合使用。

分配非托管內存

要分配非托管內存,可以使用 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 資料庫,可用於動態生成漂亮的 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 資料包管理器 控制台或使用 Visual Studio 資料包管理器安裝。

dotnet add package IronPdf
// Or
Install-Package IronPdf

C# Span (開發者運作方式):圖 1 - 使用 NuGet 資料包管理器通過在 NuGet 資料包管理器的搜索欄中搜尋 ironpdf 來安裝 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

在此示例中,我們使用 SpanIronPDF 生成 PDF 文件。

輸出:

C# Span (開發者運作方式):圖 2 - 控制台輸出

生成的 PDF:

C# Span (開發者運作方式):圖 3 - PDF 輸出

許可(可用免費試用)

IronPDF 許可證信息。 此金鑰需要放置在 appsettings.json 中。

"IronPdf.LicenseKey": "your license key"

提供您的電子郵件以獲取試用許可證。

結論

C# 中的 Span 提供了一種高效的內存操作方法,在性能和靈活性方面提供了優勢。 其非擁有的、連續的特性使其特別適合於需要最小化內存分配和自複製的場景。 通過利用 Span,開發者可以在各種應用程序中實現更好的性能,從字符串操作到高性能數字處理。 通過理解其特性並考慮其限制,開發者可以安全而高效地將 Span 用於各種內存操作任務。 結合 IronPDF 資料庫概覽,可用於生成不帶 await 和 yield 邊界的極佳 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 的限制包括對連續記憶體的要求、缺乏自動記憶體管理和垃圾回收、不支持非連續記憶體。開發人員必須手動確保記憶體的有效性和界限。

如何在 C# 中安裝 IronPDF 以進行 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 熱衷於創建直觀且美觀的用戶界面,喜歡使用現代框架並打造結構良好、視覺吸引人的手冊。

除了開發之外,Curtis 對物聯網 (IoT) 有著濃厚的興趣,探索將硬體和軟體結合的創新方式。在閒暇時間,他喜愛遊戲並構建 Discord 機器人,結合科技與創意的樂趣。