.NET 幫助

C# ConfigureAwait(它如何為開發人員工作)

作為開發人員,非同步編程可以帶來極大的好處,它可以提升應用程式的性能、效率和響應能力,特別是那些涉及需要不定時間才能完成的操作的應用程式。 透過使用`ConfigureAwait(false)`,您可以避免在某些情況下發生死鎖。 死鎖發生在非同步程式設計中,當存在同步上下文(如桌面應用程式中的 UI 執行緒)時,它期望在繼續之前完成某個操作。 不過,仍需等待同步上下文可用,這會導致一個循環等待。

今天,我們將探討如何使用ConfigureAwait與IronPDF合作,透過非同步編程來高效執行PDF處理任務。 IronPDF 是一個 .NET PDF 庫,使與 PDF 相關的任務變得輕而易舉。 擁有強大的功能集,良好的跨平台兼容性和廣泛的文檔,它是一個值得擁有的強大 PDF 工具,在您的開發工具箱中必不可少。

理解 C# 中的異步編程

什麼是非同步程式設計?

非同步程式設計是指撰寫程式碼的一種方法,使某些操作能夠獨立於主應用程式執行緒運行。 這對於需要等待的長時間任務很有用,例如 I/O 操作。 通過允許這些任務在不阻塞主線程的情況下運行,應用程式可以在這些任務需要時間完成的同時繼續運行,最終提高應用程式的性能和響應速度。

在非同步程式碼中 ConfigureAwait 的角色

ConfigureAwait 是一種在非同步程式設計中使用的方法,用於控制延續是如何執行的。 繼續是指在 await 表達式之後運行的代碼,預設情況下,**await** 會捕捉當前上下文並嘗試將繼續傳送回該上下文,這可能效率不高。ConfigureAwait 允許您指定繼續是否應在捕捉的上下文中運行,用 **ConfigureAwait(true)** 表示,或不在該上下文中運行,用 **ConfigureAwait(false)** 表示。

使用 `ConfigureAwait(false)` 可以避免死鎖,這是因為當您使用它時,您是在告訴任務不要捕獲當前的同步上下文,也不要嘗試在原始上下文上恢復。 這樣就允許續集在線程池線程而不是原始上下文中運行,從而防止主線程被阻塞。

`ConfigureAwait(false)` 特別適用於程式庫代碼或在恢復原始上下文不必要的情況下,從而確保代碼保持靈活且無死鎖。

如何使用ConfigureAwait與IronPDF一起配置

在你的.NET專案中設置IronPDF

要在您的 .NET 專案中開始使用 IronPDF,首先安裝 IronPDF NuGet 套件。 您可以藉由導航至工具 > NuGet 套件管理員 > 解決方案的 NuGet 套件管理員,並搜尋 IronPDF 來完成此操作:

C# ConfigureAwait(它如何對開發人員發揮作用):圖1

或者,在套件管理控制台中執行以下命令:

Install-Package IronPdf
Install-Package IronPdf
'INSTANT VB TODO TASK: The following line uses invalid syntax:
'Install-Package IronPdf
$vbLabelText   $csharpLabel

要開始在程式碼中使用IronPDF,請確保您已在程式碼檔案的頂部放置**using IronPdf**語句。欲獲得更詳細的IronPDF環境設置指南,請查看其快速入門頁面。

使用 IronPDF 非同步生成 PDF 文件

異步生成 PDF 文件在需要生成大量 PDF 文件或想同時執行多個操作的情況下尤其有利。 使用IronPDF,您可以非同步地執行與PDF相關的任務,其可能看起來像以下這段非同步程式碼:

using IronPdf;
using System.Threading.Tasks;
class program
{
    static async Task Main(string[] args)
    {
        await GeneratePdfAsync();
    }
    static async Task GeneratePdfAsync()
    {
        ChromePdfRenderer renderer = new ChromePdfRenderer();
        string htmlContent = "<h1>Hello World!</h1>";
        PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);
        await Task.Run(() => pdf.SaveAs("outputAsync.pdf"));
        Console.WriteLine("Working!");
    }
}
using IronPdf;
using System.Threading.Tasks;
class program
{
    static async Task Main(string[] args)
    {
        await GeneratePdfAsync();
    }
    static async Task GeneratePdfAsync()
    {
        ChromePdfRenderer renderer = new ChromePdfRenderer();
        string htmlContent = "<h1>Hello World!</h1>";
        PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);
        await Task.Run(() => pdf.SaveAs("outputAsync.pdf"));
        Console.WriteLine("Working!");
    }
}
Imports IronPdf
Imports System.Threading.Tasks
Friend Class program
	Shared Async Function Main(ByVal args() As String) As Task
		Await GeneratePdfAsync()
	End Function
	Private Shared Async Function GeneratePdfAsync() As Task
		Dim renderer As New ChromePdfRenderer()
		Dim htmlContent As String = "<h1>Hello World!</h1>"
		Dim pdf As PdfDocument = Await renderer.RenderHtmlAsPdfAsync(htmlContent)
		Await Task.Run(Function() pdf.SaveAs("outputAsync.pdf"))
		Console.WriteLine("Working!")
	End Function
End Class
$vbLabelText   $csharpLabel

在此代碼中,我們在 GeneratePdfAsync() 方法中異步創建了一個 PDF 文檔。 ChromePdfRenderer 用於創建渲染器,它在從 HTML 內容創建 PDF 文件時至關重要。 PdfDocument 類別用於從提供的HTML 字串創建 PDF,然而,你也可以使用它從HTML 檔案URL影像等創建 PDF。 欲了解有關使用 IronPDF 生成 PDF 的不同方法,請查看生成 PDF 的操作指南

異步處理大型 PDF 文件

當處理大型 PDF 檔案時,使用異步方法並搭配 `ConfigureAwait(false)` 可以透過在長時間操作期間釋放主執行緒來顯著提升效能。 在此範例中,我選取了一個大型 PDF 文件,並執行了文字擷取任務,以展示非同步 PDF 處理的好處。

using IronPdf;
using System.Threading.Tasks;
using System.IO;
using System;
class Program
{
    static async Task Main(string[] args)
    {
        await LongPdfTask();
    }
    static async Task LongPdfTask()
    {
        try
        {
            // Initialize IronPDF's PdfDocument
            PdfDocument pdf = await Task.Run(() => PdfDocument.FromFile("Sample.pdf")).ConfigureAwait(false);
            // Extract text from PDF asynchronously
            string text = await Task.Run(() => pdf.ExtractAllText()).ConfigureAwait(false);
            // Write the extracted text to a file asynchronously
            await Task.Run(() => File.WriteAllText("extractedText.txt", text)).ConfigureAwait(false);
            Console.WriteLine("Extraction complete!");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error in GeneratePdfAsync: {ex.Message}");
        }
    }
}
using IronPdf;
using System.Threading.Tasks;
using System.IO;
using System;
class Program
{
    static async Task Main(string[] args)
    {
        await LongPdfTask();
    }
    static async Task LongPdfTask()
    {
        try
        {
            // Initialize IronPDF's PdfDocument
            PdfDocument pdf = await Task.Run(() => PdfDocument.FromFile("Sample.pdf")).ConfigureAwait(false);
            // Extract text from PDF asynchronously
            string text = await Task.Run(() => pdf.ExtractAllText()).ConfigureAwait(false);
            // Write the extracted text to a file asynchronously
            await Task.Run(() => File.WriteAllText("extractedText.txt", text)).ConfigureAwait(false);
            Console.WriteLine("Extraction complete!");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error in GeneratePdfAsync: {ex.Message}");
        }
    }
}
Imports IronPdf
Imports System.Threading.Tasks
Imports System.IO
Imports System
Friend Class Program
	Shared Async Function Main(ByVal args() As String) As Task
		Await LongPdfTask()
	End Function
	Private Shared Async Function LongPdfTask() As Task
		Try
			' Initialize IronPDF's PdfDocument
			Dim pdf As PdfDocument = Await Task.Run(Function() PdfDocument.FromFile("Sample.pdf")).ConfigureAwait(False)
			' Extract text from PDF asynchronously
			Dim text As String = Await Task.Run(Function() pdf.ExtractAllText()).ConfigureAwait(False)
			' Write the extracted text to a file asynchronously
			Await Task.Run(Sub() File.WriteAllText("extractedText.txt", text)).ConfigureAwait(False)
			Console.WriteLine("Extraction complete!")
		Catch ex As Exception
			Console.WriteLine($"Error in GeneratePdfAsync: {ex.Message}")
		End Try
	End Function
End Class
$vbLabelText   $csharpLabel

在上述代碼中,**ConfigureAwait(false)** 用於從一個龐大的 PDF 文件中提取所有文字這項耗時的工作,我們的例子中該文件超過 200 頁。

  • 匯入和設置:我們代碼頂部的第一個部分專門用於匯入必要的庫和命名空間。 您需要確保您有 `using IronPdf` 才能使用 IronPDF 函式庫
  • Class 和 Main 方法: `class Program` 定義了包含此專案主要應用程式碼的類別。 **static async Task Main(string[] args)** 是應用程式的入口點。 在這裡,我們將其標記為async,因此我們的異步操作可以在其中運行。 然後,我們使用await LongPdfTask()以非同步方式調用 LongPdfTask 方法。
  • 嘗試區塊:我已將代碼封裝在LongPdfTask方法中的try-catch區塊內,以優雅地處理任何意外的例外情況。

    • PdfDocument PDF = await Task.Run(() => PdfDocument.FromFile("Sample.pdf")).ConfigureAwait(false): 該行可以分為三個不同的部分:

      • PdfDocument.FromFile("Sample.pdf"): 此部分會同步將指定的 PDF 檔案載入到 IronPdf.PdfDocument 對象中。

      • await Task.Run(() => ...): 在單獨的執行緒上運行 PDF 加載操作,以避免阻塞主執行緒。 這使其成為非同步操作。
  • .ConfigureAwait(false):避免捕獲當前上下文,這應該可以提高性能並減少死鎖。
  • string text = await Task.Run(() => pdf.ExtractAllText()).ConfigureAwait(false): 這會執行 IronPDF 文字提取 方法,ExtractAllText()。 再次使用 await Task.Run(() => ...) 在單獨的執行緒上異步執行此操作。
  • await Task.Run(() => File.WriteAllText("extractedText.txt", text)).ConfigureAwait(false): 使用此方法,我們再次使用 await Task 方法,將提取的文字異步寫入一個 .txt 文件。

之前

C# ConfigureAwait(對開發者的運作方式):圖 2

輸出

C# ConfigureAwait(它對開發人員的工作原理):圖 3

在 .NET 應用程式中使用 ConfigureAwait 的最佳實踐

何时使用 ConfigureAwait(true) 与 ConfigureAwait(false)

ConfigureAwait(false) 最適合在庫程式碼或背景處理中使用,此時不需要保留同步上下文。 通常,這是用於伺服器端程式碼,性能至關重要。 使用ConfigureAwait(false)表示當await操作完成時,後續操作不一定在啟動異步操作的相同執行緒上運行。

在 PDF 處理方面,實施ConfigureAwait(false)可以在執行多個 PDF 處理任務時幫助最大化性能,以避免因上下文切換而導致的瓶頸。 它還可以在處理大量 PDF 文件時幫助應用程式順利運行,並幫助在您工作於控制台應用程式或背景服務中時維持效率,而在此情況下的上下文切換可能是不必要的。

ConfigureAwait(true) 最好用於 UI、任何單元測試或 ASP.NET 應用程式中,這些情況下後續操作必須在相同上下文中運行,儘管使用不當可能導致死鎖。 例如,如果您正在更新 UI 或訪問 httpcontext)。 ConfigureAwait(true) 是預設行為,也可以簡寫為 ConfigureAwait

當用於 PDF 處理任務時,尤其在以下情況下特別有用:如果您的 PDF 處理代碼與 UI 緊密集成(當使用像 WPF、WinForms 等 UI 應用程式時),例如顯示進度,而且您需要捕獲同步上下文以確保這些更新在 UI 執行緒上發生。 這對於需要由於執行緒關聯性要求必須在特定執行緒上執行的執行緒敏感操作也是有益的。

在非同步 IronPDF 操作中處理例外狀況

在異步編程中處理異常是一個需要注意的重要方面,需要小心考慮,未處理的異常可能會終止應用程式。 在異步代碼周圍使用 try-catch 區塊是優雅處理任何意外異常的好方法。

例如:

public async Task SafeGeneratePdfAsync()
{
    try
    {
        ChromePdfRenderer renderer = new ChromePdfRenderer();
        PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync("<h1>Error Handling</h1>").ConfigureAwait(false);
        await Task.Run(() => pdf.SaveAs("output.pdf")).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An error occurred: {ex.Message}");
    }
}
public async Task SafeGeneratePdfAsync()
{
    try
    {
        ChromePdfRenderer renderer = new ChromePdfRenderer();
        PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync("<h1>Error Handling</h1>").ConfigureAwait(false);
        await Task.Run(() => pdf.SaveAs("output.pdf")).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An error occurred: {ex.Message}");
    }
}
Public Async Function SafeGeneratePdfAsync() As Task
	Try
		Dim renderer As New ChromePdfRenderer()
		Dim pdf As PdfDocument = Await renderer.RenderHtmlAsPdfAsync("<h1>Error Handling</h1>").ConfigureAwait(False)
		Await Task.Run(Function() pdf.SaveAs("output.pdf")).ConfigureAwait(False)
	Catch ex As Exception
		Console.WriteLine($"An error occurred: {ex.Message}")
	End Try
End Function
$vbLabelText   $csharpLabel

當使用帶有 `ConfigureAwait(false)` 的延續任務時,可以在延續內使用 try-catch 來處理異常,或者如果使用 Task.ContinueWith,則可以通過 Task.Exception 屬性來處理。

您可以編寫代碼來執行此操作的示例如下所示:

class program
{
    public static async Task Main(string[] args)
    {
        await ProcessPdfWithContinuationAsync();
    }
    static Task ProcessPdfWithContinuationAsync()
    {
        return Task.Run(() => PdfDocument.FromFile("Sample.pdf"))
            .ContinueWith(pdfTask =>
            {
                if (pdfTask.IsFaulted)
                {
                    // Handle exceptions from loading the PDF
                    Console.WriteLine($"Error loading PDF: {pdfTask.Exception?.GetBaseException().Message}");
                    return;
                }
                var pdf = pdfTask.Result;
                // Extract text asynchronously with exception handling
                Task.Run(() => pdf.ExtractAllText())
                    .ContinueWith(extractTask =>
                    {
                        if (extractTask.IsFaulted)
                        {
                            // Handle exceptions from extracting text
                            Console.WriteLine($"Error extracting text: {extractTask.Exception?.GetBaseException().Message}");
                            return;
                        }
                        // Proceed if text extraction is successful
                        Console.WriteLine("Extracted text:");
                        Console.WriteLine(extractTask.Result);
                    }, TaskContinuationOptions.OnlyOnRanToCompletion);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);
    }
class program
{
    public static async Task Main(string[] args)
    {
        await ProcessPdfWithContinuationAsync();
    }
    static Task ProcessPdfWithContinuationAsync()
    {
        return Task.Run(() => PdfDocument.FromFile("Sample.pdf"))
            .ContinueWith(pdfTask =>
            {
                if (pdfTask.IsFaulted)
                {
                    // Handle exceptions from loading the PDF
                    Console.WriteLine($"Error loading PDF: {pdfTask.Exception?.GetBaseException().Message}");
                    return;
                }
                var pdf = pdfTask.Result;
                // Extract text asynchronously with exception handling
                Task.Run(() => pdf.ExtractAllText())
                    .ContinueWith(extractTask =>
                    {
                        if (extractTask.IsFaulted)
                        {
                            // Handle exceptions from extracting text
                            Console.WriteLine($"Error extracting text: {extractTask.Exception?.GetBaseException().Message}");
                            return;
                        }
                        // Proceed if text extraction is successful
                        Console.WriteLine("Extracted text:");
                        Console.WriteLine(extractTask.Result);
                    }, TaskContinuationOptions.OnlyOnRanToCompletion);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);
    }
Friend Class program
	Public Shared Async Function Main(ByVal args() As String) As Task
		Await ProcessPdfWithContinuationAsync()
	End Function
	Private Shared Function ProcessPdfWithContinuationAsync() As Task
		Return Task.Run(Function() PdfDocument.FromFile("Sample.pdf")).ContinueWith(Sub(pdfTask)
				If pdfTask.IsFaulted Then
					' Handle exceptions from loading the PDF
					Console.WriteLine($"Error loading PDF: {pdfTask.Exception?.GetBaseException().Message}")
					Return
				End If
				Dim pdf = pdfTask.Result
				' Extract text asynchronously with exception handling
				Task.Run(Function() pdf.ExtractAllText()).ContinueWith(Sub(extractTask)
						If extractTask.IsFaulted Then
							' Handle exceptions from extracting text
							Console.WriteLine($"Error extracting text: {extractTask.Exception?.GetBaseException().Message}")
							Return
						End If
						' Proceed if text extraction is successful
						Console.WriteLine("Extracted text:")
						Console.WriteLine(extractTask.Result)
				End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
		End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
	End Function
$vbLabelText   $csharpLabel

為何選擇IronPDF來滿足您的PDF處理需求?

IronPDF 的主要功能和優勢

C# ConfigureAwait(對開發者的工作原理):圖 4

IronPDF 是一個功能強大的 C# PDF 函式庫,提供豐富的功能集以滿足您所有與 PDF 相關的任務。 全力支援 .NET 8、7、6、.NET Core、Standard 和 Framework,並能在多種應用環境中運行,如 Windows、Linux、Mac、Docker、Azure 和 AWS,無論您偏好的環境是什麼,您都能充分利用 IronPDF。

使用IronPDF,您可以從各種文件和數據類型生成PDF;包括HTML文件HTML字串URLs圖片DOCXRTF,通常只需幾行程式碼! 它可以處理 PDF 文件的格式化,應用自訂浮水印合併和分割 PDF,處理PDF 加密安全性,等等。

IronPDF 對非同步程式設計的支援

IronPDF 提供許多操作的非同步方法,使開發者能夠無縫地利用 async/await 模式。 這種支持確保了IronPDF可以被整合到性能關鍵的應用中而不影響響應速度,使其成為在異步環境中從事PDF相關任務的開發人員不可或缺的PDF工具。

授權

如果您想親自試用IronPDF並探索其廣泛的功能範圍,可以透過其免費試用期輕鬆做到。 安裝快速且簡便,您可以在短時間內將 IronPDF 應用於您的 PDF 項目中。想繼續使用它並利用其強大的功能提升您的 PDF 技術嗎? 許可證起價只需$749,並附有30天的退款保證、一整年的產品支持與更新,且是永久許可證(不需擔心煩人的續費!)

C# ConfigureAwait(開發者如何運作):圖 5

範例:使用 ConfigureAwait 和 IronPDF 進行 PDF 生成

若要非同步地生成 PDF,我們將使用 IronPDF 執行程式碼來渲染 HTML 檔案,並儲存結果,同時使用 ConfigureAwait(false) 以確保後續步驟不會不必要地切換回原始的同步上下文。

using IronPdf;
using System.Threading.Tasks;
using System.IO;
using System;
class program
{
    public static async Task Main(string[] args)
    {
        await CreateInvoicePdfAsync();
    }
    static async Task<string> CreateInvoicePdfAsync()
    {
        ChromePdfRenderer renderer = new ChromePdfRenderer();
        try
        {
            var pdf = await renderer.RenderHtmlFileAsPdfAsync("example.html").ConfigureAwait(false);
            await Task.Run(() => pdf.SaveAs("invoice.pdf")).ConfigureAwait(false);
            return filePath;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error generating PDF: {ex.Message}");
            return null;
        }
    }
}
using IronPdf;
using System.Threading.Tasks;
using System.IO;
using System;
class program
{
    public static async Task Main(string[] args)
    {
        await CreateInvoicePdfAsync();
    }
    static async Task<string> CreateInvoicePdfAsync()
    {
        ChromePdfRenderer renderer = new ChromePdfRenderer();
        try
        {
            var pdf = await renderer.RenderHtmlFileAsPdfAsync("example.html").ConfigureAwait(false);
            await Task.Run(() => pdf.SaveAs("invoice.pdf")).ConfigureAwait(false);
            return filePath;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error generating PDF: {ex.Message}");
            return null;
        }
    }
}
Imports IronPdf
Imports System.Threading.Tasks
Imports System.IO
Imports System
Friend Class program
	Public Shared Async Function Main(ByVal args() As String) As Task
		Await CreateInvoicePdfAsync()
	End Function
	Private Shared Async Function CreateInvoicePdfAsync() As Task(Of String)
		Dim renderer As New ChromePdfRenderer()
		Try
			Dim pdf = Await renderer.RenderHtmlFileAsPdfAsync("example.html").ConfigureAwait(False)
			Await Task.Run(Function() pdf.SaveAs("invoice.pdf")).ConfigureAwait(False)
			Return filePath
		Catch ex As Exception
			Console.WriteLine($"Error generating PDF: {ex.Message}")
			Return Nothing
		End Try
	End Function
End Class
$vbLabelText   $csharpLabel

在這個例子中,我們使用建立的非同步方法,static async TaskCreateInvoicePdfAsync(),從 RenderHtmlFileAsPdfAsync 方法提供的 HTML 檔案生成 PDF 發票。 我們使用ConfigureAwait(false)來防止這個任務在原始同步上下文中繼續,從而提高我們非UI應用程式的性能。

我們也再次實現了 await Task.Run()) => ...) 方法以異步運行操作。 最後,我們使用 pdf.SaveAs 方法將新生成的 PDF 文件保存為 "invoice.pdf"。 CreateInvoicePdfAsync() 方法中的整段代碼已被包裹在 try-catch 塊中,以處理任何意外的異常。

HTML檔案

C# ConfigureAwait(它是如何為開發人員工作的):圖 6

輸出

C# ConfigureAwait(開發人員如何使用):圖 7

正如您所見,我們已成功將 HTML 文件異步生成 PDF,並且它為我們創建了一個清晰、高品質的 PDF 文件。

結論

非同步程式設計對於構建響應迅速且高效的 .NET 應用程式至關重要,正確使用ConfigureAwait 可以幫助您實現最佳效能,尤其是在撰寫應用層級代碼時。 在使用IronPDF時,利用非同步方法和ConfigureAwait(false)可確保您的 PDF 處理任務不會阻塞主線程,從而提高應用程式的整體響應能力。 通過了解何時以及如何使用ConfigureAwait,您可以使您的IronPDF PDF處理任務更加穩健和性能友好。

現在您可以作為專業人員開始在非同步程式設計中使用ConfigureAwait與IronPDF,那還等什麼呢? 立即試用IronPDF,看看它如何改善您與PDF相關的項目! 如果您想了解更多有關IronPDF作為強大多用途函式庫程式碼所提供的廣泛功能,請務必查看其便捷的操作指南。 或者,如果您想了解更多關於將IronPDF與異步編程方法一起使用的內容,或者只是想更了解IronPDF,請查看我們的博客文章。 如果您正在尋找更多有關非同步 PDF 生成的範例,請查看我們的C# Wait For Seconds文章,或我們的另一篇C# Task.Run

Chipego
奇佩戈·卡林达
軟體工程師
Chipego 擁有天生的傾聽技能,這幫助他理解客戶問題,並提供智能解決方案。他在獲得信息技術理學學士學位後,于 2023 年加入 Iron Software 團隊。IronPDF 和 IronOCR 是 Chipego 專注的兩個產品,但隨著他每天找到新的方法來支持客戶,他對所有產品的了解也在不斷增長。他喜歡在 Iron Software 的協作生活,公司內的團隊成員從各自不同的經歷中共同努力,創造出有效的創新解決方案。當 Chipego 離開辦公桌時,他常常享受讀好書或踢足球的樂趣。
< 上一頁
Azure Tables(對開發人員的運作方式)
下一個 >
C# 可空類型 (開發者如何使用)