跳至頁尾內容
.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. 在字串中搜尋

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 執行時的垃圾收集器管理的記憶體,通常涉及使用原生記憶體分配和釋放。 以下是 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 分配了一個不受管理的記憶體塊,然後使用從 unmanaged memory 獲得的指標建立了一個 Span<byte>。 這使我們可以使用熟悉的 Span API 與不受管理的記憶體進行交互。 重要的是要注意,在操作不受管理的記憶體時,您有責任管理記憶體的分配和釋放。

複製資料到和從非管理記憶體

Span 提供了如 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 操作:

在處理非同步 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 程式庫概述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 (How It Works For Developers): Figure 1 - Install IronPDF using NuGet Package Manager by searching IronPDF in the search bar of NuGet Package Manager

using System;
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操作?

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核心程式碼庫的原開發者,他從創立以來就一直在塑造公司的產品架構,與首席執行官Cameron Rimington一起將公司轉變為服務於NASA、特斯拉和全球政府公司的50多名人員的公司。

Jacob擁有曼徹斯特大學的土木工程一等榮譽學士學位(BEng),於1998-2001年之間獲得。在1999年於倫敦創辦他的第一家軟體公司並於2005年建立了他的第一批.NET元組件後,他專注於解決Microsoft生態系統中的複雜問題。

他的旗艦IronPDF和Iron Suite .NET程式庫在全球獲得了超過3000萬次NuGet安裝依據,他的基礎程式碼基繼續支援著世界各地開發者使用的工具。擁有25年的商業經驗和41年的程式設計專業知識,他仍專注於推動企業級C#、Java和Python PDF技術的創新,同時指導下一代技術領導者。

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話