跳過到頁腳內容
.NET幫助

C# Span(對於開發者的運行原理)

Span 是 C# 7.2 中引入的一種類型,是 System 命名空間中 Span 結構的一部分。 其設計目的在於表示任意記憶體的連續區域。 與管理堆等陣列或集合不同,Span 並不擁有堆疊記憶體或其指向的記憶體區域; 相反地,它提供了一個輕量級的檢視,超過現有的記憶區塊。 此特性使得 Span 在需要有效率地使用記憶體緩衝區,而不會產生額外開銷和不安全程式碼的情況下,功能特別強大。 在本文後面,我們還將看到 Iron SoftwareIronPDF 函式庫的介紹。

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 支援分片,可讓您建立引用原始記憶體部分的子跨區。

範例

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.在字串內搜尋

ReadOnlySpanIndexOf() 在字串內搜尋。

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 提供了一種更有效率地處理字串的方式,尤其是在應該盡量減少記憶體分配和複製的情境中。 它是優化效能關鍵程式碼的強大工具,在處理大量字串資料時尤其有利。

跨度限制

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,確保您的目標平台支援 Span 是至關重要的。

2.非連續記憶體緩衝區

2.1 對非連續記憶體的有限支援

ReadOnlySpan 主要設計用來與連續的記憶體區塊或緩衝區無縫運作。 對於涉及非連續記憶體緩衝區或記憶體有空隙的結構的場景,可能不是最適合的選擇。

2.2結構限制

某些依賴於非連續記憶體的資料結構或情境可能與 ReadOnlySpan 不太一致。 例如,由於 ReadOnlySpan 的連續記憶體需求,像連結清單或圖形結構這樣的資料結構可能不太適合。

2.3複雜指標操作

在涉及非連續記憶體的情況下,特別是那些需要複雜的指標運算的情況下,ReadOnlySpan 可能無法提供與 C++ 等語言中原始指標相同的低階控制和彈性。 在這種情況下,利用不安全代碼與指針可能更為合適。

2.4 某些 API 缺乏直接支援。

與連續記憶體相似,需要注意的是,並非所有 API 或函式庫都可能直接支援以 ReadOnlySpan 表示的非連續記憶體。 為了適應這些情況,可能需要額外的中間步驟或轉換,以確保相容性。

跨和非管理記憶體

在 C# 中,Span 可以有效地與非管理記憶體搭配使用,以受控且有效率的方式執行與記憶體相關的作業。 非管理記憶體指的是未被 .NET runtime 的垃圾回收器管理的記憶體,它通常涉及使用本機記憶體分配和取消分配。 以下是 Span 如何利用 C# 中的非管理記憶體。

分配非管理記憶體

若要分配非管理記憶體,您可以使用 System.Runtime.InteropServices.MemoryMarshal 類別。 Marshal.AllocHGlobal 方法會分配記憶體,並傳回已分配區塊的指標。 分配的記憶體或記憶體位址會以非管理記憶體指針 (unmanagedMemory pointer) 的方式持有,並會具有讀寫存取權。 可輕鬆存取記憶體的連續區域。

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 提供了像 Slice, CopyToToArray 之類的方法,可用於在受管理記憶體和非受管理記憶體之間有效地複製資料。

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 操作: 2.

在處理異步 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. 異步檔案作業: 3.

與 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. 異步任務處理: 4.

在處理產生或消耗資料的異步任務時,您可以使用 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 函式庫概述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 package manager for IronPDF 控制台或 Visual Studio package manager 進行安裝。

dotnet add package IronPdf
// Or
Install-Package IronPdf

C# Span (How It Works For Developers):圖 1 - 使用 NuGet Package Manager 安裝 IronPDF,方法是在 NuGet Package Manager 的搜尋列中搜尋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 文件。

Output:

!a href="/static-assets/pdf/blog/csharp-span/csharp-span-2.webp">C# Span (How It Works For Developers):圖 2 - 控制台輸出

生成的 PDF:

C# Span (How It Works For Developers):圖 3 - PDF 輸出

授權(可免費試用)

IronPDF授權資訊。 此 key 需要放在 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 用於資料不該被修改的情況,提供類似的效能優點,同時確保資料的完整性。

在 C# 中,Span 可以與非管理記憶體一起使用嗎?

是的,Span 可透過從指向非管理記憶體的指標建立一個 span 來使用非管理記憶體。這允許直接操作記憶體,同時使用 Marshal.AllocHGlobalMarshal.FreeHGlobal 等方法確保記憶體被正確地分配和取消分配。

IronPDF 如何與 Span 整合以產生 PDF?

IronPDF 可與 Span 並肩工作,透過有效管理記憶體和避免不必要的分配,動態產生 PDF。此整合可讓開發人員從網頁內容建立 PDF 文件,並改善效能。

使用 Span 進行記憶體管理有哪些限制?

使用 Span 的限制包括對連續記憶體的要求、缺乏自動記憶體管理和垃圾回收,以及不支援非連續記憶體。開發人員必須手動確保記憶體的有效性和邊界。

如何在 C# 中安裝 IronPDF 進行 PDF 操作?

IronPDF 可使用 NuGet 包管理器安裝在 C# 專案中。使用 dotnet add package IronPdfInstall-Package IronPdf 等命令將其新增至您的專案。

使用 Span 進行字串操作有什麼好處?

Span 透過最小化記憶體分配與複製,允許有效率的字串操作。這對於需要處理大量字串資料的效能關鍵程式碼尤其有利。

IronPDF 是否有試用版?

是的,IronPDF 提供試用授權,您可以透過提供電子郵件取得試用授權。試用授權金鑰必須放在 appsettings.json 檔案中,才能使用該函式庫。

Span 可以用於異步方法呼叫嗎?

是的,Span 可用於異步方法呼叫,以有效率地處理資料,而不會產生不必要的複製。這在 I/O 作業和檔案處理中特別有用,可利用 MemorySpan

Jacob Mellor, Team Iron 首席技术官
首席技术官

Jacob Mellor 是 Iron Software 的首席技術官,作為 C# PDF 技術的先鋒工程師。作為 Iron Software 核心代碼的原作者,他自開始以來塑造了公司產品架構,與 CEO Cameron Rimington 一起將其轉變為一家擁有超過 50 名員工的公司,為 NASA、特斯拉 和 全世界政府機構服務。

Jacob 持有曼徹斯特大學土木工程一級榮譽学士工程學位(BEng) (1998-2001)。他於 1999 年在倫敦開設了他的第一家軟件公司,並於 2005 年製作了他的首個 .NET 組件,專注於解決 Microsoft 生態系統內的複雜問題。

他的旗艦產品 IronPDF & Iron Suite .NET 庫在全球 NuGet 被安裝超過 3000 萬次,其基礎代碼繼續為世界各地的開發工具提供動力。擁有 25 年的商業經驗和 41 年的編碼專業知識,Jacob 仍專注於推動企業級 C#、Java 及 Python PDF 技術的創新,同時指導新一代技術領袖。