﻿# 如何在 .NET Core、.NET 5+ 與 ASP.NET Core 正確使用 Big5 編碼

由於從 .NET Core 1.0 開始，就沒有自動載入 `BIG5` 編碼的 `Encoding` 資料，所以你沒辦法直接透過 `Encoding.GetEncoding("Big5")` 取得 `Encoding` 物件。在 .NET Core 3.1 之前，還需要須額外安裝 `System.Text.Encoding.CodePages` 套件才行。不過，從 .NET 5 開始，這個套件成為了 .NET SDK 的一部分，不再需要額外安裝。但即便如此，你還是要特別執行一段註冊以使用此編碼。接下來，我們將深入探討如何註冊這些編碼並正確使用它們在不同的場景中。

![An abstract digital artwork representing text encoding and character conversion in .NET Core.](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/bee8a09f123722addcbc-ad0d4539-0fde-4ed1-86d6-dbb9cbf07b3c.webp)

### 快速摘要

如果需要取得 `Encoding.GetEncoding("Big5")` 編碼，請在程式啟動時加入以下程式碼：

```csharp
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
```

### Encoding 概述

`System.Text.Encoding` 是 .NET 中用於文字編碼轉換的核心類別。它的作用是在**字元**和**位元組**之間進行轉換：所謂**編碼** ([Encoding](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-text-encoding#:~:text=The%20Encoding%20class%20represents%20a,character%20encoding)) 就是將一組 Unicode 字元轉換成對應的位元組序列，反之則稱為**解碼** (Decoding)。

由於 .NET 字串 (`string`) 內部使用 `UTF-16` 編碼 (Unicode 編碼) 來表示文字，而每個 `char` 通常對應 Unicode **碼元**(`Code Unit`)，`Encoding` 類別的角色就是在程式的 Unicode 字串與各種外部編碼形式之間架起橋樑。例如，讀取檔案或網路資料時，可能需要將**位元組**依照正確的編碼解碼成 **.NET 字串**；反之，寫出文字或傳輸資料時，也需要依照指定編碼將**字串**轉為**位元組**。

需要注意的是，`Encoding` 類別設計用來處理**文字**資料，而非任意的二進位內容。如果要將任意二進位資料轉成字串儲存 (例如將圖片或加密資料以字串表示)，不應直接使用文字編碼 (否則可能造成資料遺失或損毀)，而應採用例如 `Base64` 等專門的編碼方式。

### 碼點 (code point) 與 碼元 (code unit)

**碼點** (code point) 與 **碼元** (code unit) 是字元編碼領域中的兩個重要概念，它們在字元表示和儲存中扮演著不同的角色。

-   碼點 (Code point)
    
    是指在編碼字元集中，每個字元所對應的唯一數值。例如，在 Unicode 中，字元 "A" 的碼點是 `U+0041`。
    
-   碼元 (code unit)
    
    則是指在特定編碼形式中，用於表示碼點的最小資料單位。
    
    不同的編碼方式，其碼元的大小可能不同。例如，在 UTF-8 編碼中，每個碼元為 8 位元；而在 UTF-16 編碼中，每個碼元為 16 位元。
    

簡單來說，碼點 (code point) 是對應於字元的**抽象數值**，而 碼元 (code unit) 則是**具體編碼方式中**用來**表示這些碼點的實際資料單位**。

### 常見的編碼類型

常見的文字編碼方式有許多種，各自適用於不同情境。以下列出幾種常用編碼及其特性：

-   **UTF-8**
    
    一種 Unicode (萬國碼) 的實作，使用**可變長度**的**位元組序列**來編碼字元，英文字母等基本字元只需 1 個位元組，歐洲及中東字元通常 2 個位元組，東亞文字可能需要 3~4 個位元組。
    
    UTF-8 能表示所有 Unicode 字元，具有良好的儲存效率和廣泛的相容性，因此成為網際網路及跨平台傳輸的事實標準。以 UTF-8 編碼的文字沒有語系限制，非常適合多語言環境。
    
-   **UTF-16**
    
    Unicode 的 16 位元編碼形式，每個字元通常使用 2 個位元組表示。
    
    依照位元序不同可分為 **UTF-16 LE** (Little Endian，小端序) 與 **UTF-16 BE** (Big Endian，大端序)。
    
    在 Windows 等主流平台上常用 `UTF-16 LE` (.NET 的 `Encoding.Unicode` 屬性即代表 UTF-16 LE 編碼。
    
    UTF-16 同樣可表示全部 Unicode 字元，但因固定每字元至少 2 位元組，對以**拉丁字母**為主的內容來說檔案體積會比 UTF-8 大一些。
    
    UTF-16 BE 則主要在某些**大型主機**或**特定系統**使用，使用時須注意大小端序需與資料對應。
    
-   **UTF-32**
    
    Unicode 的 32 位元編碼形式，每個字元固定使用 4 個位元組表示 (可直接對應 Unicode 碼點)。
    
    `Encoding.UTF32` 提供了 UTF-32 LE 編碼（以及透過建構函式可建立 UTF-32 BE 編碼。
    
    UTF-32 因為每個字元占用空間大，一般較少用於儲存或傳輸，但在需要固定長度表示字元的場景下可能會用到。
    
-   **ASCII**
    
    一種早期的 7 位元編碼，只定義了 128 個字元 (包括英文字母、數字、標點符號和控制字元)。
    
    在 .NET 中可透過 `Encoding.ASCII` 使用此編碼。ASCII 編碼不支援中文、日文等任何超出 `U+007F` 的字元，所以**僅適用於純英文環境**。
    
    如內容包含非 ASCII 字元，使用 ASCII 編碼將無法正確表示，**未支援的字元**通常會**被遺失**或**替換成問號**。
    
-   **ANSI / 碼頁 (Code Page)**
    
    `ANSI` 並非一種單一定義的編碼，而是**對各地區預設碼頁**的統稱。
    
    例如 美國 / 西歐 Windows 的預設 ANSI 編碼是 `Windows-1252` (類似 `ISO-8859-1` 西歐編碼)，繁體中文 Windows 的 ANSI 編碼則是 `Big5`，日文 Windows 則是 `Shift-JIS` 等。
    
    這類編碼大多採用**單/雙位元組**混合的形式表示字元，也就是所謂「延伸 ASCII」或傳統**碼頁** (Code Page) 編碼。
    
    下面分別介紹幾個代表：
    
    -   **Big5**
        
        早期繁體中文地區使用的主要編碼，屬於**雙位元組**編碼。
        
        `Big5` 由台灣五大電腦公司在 1980 年代制定，名稱即取自「五大碼」 ([Big5 - Wikipedia](https://en.wikipedia.org/wiki/Big5#:~:text=Big5%20gets%20its%20name%20from,2))。
        
        `Big5` 編碼可涵蓋常用的繁體中文字以及基本的拉丁字母和符號，在台灣、香港、澳門曾廣泛用於文件、郵件和 Windows 系統預設語系。現今 `UTF-8` 普及後，`Big5` 主要在舊系統相容或歷史資料中會見到。
        
    -   **Shift-JIS**
        
        日本使用的傳統編碼 (又稱 `SJIS`)，也是一種可變長度的雙位元組編碼。
        
        `Shift-JIS` 由日本的 ASCII 公司和微軟共同制定，結合了 `JIS` 標準的單位元假名與雙位元漢字 ([Shift JIS - Wikipedia](https://en.wikipedia.org/wiki/Shift_JIS#:~:text=Shift%20JIS%20,JIS%20X%200208%20Appendix%201))。
        
        在 Unicode 普及前，`Shift-JIS` 一直是日文 Windows 和網站的主要編碼 ([Shift JIS - Wikipedia](https://en.wikipedia.org/wiki/Shift_JIS#:~:text=Shift%20JIS%20,JIS%20X%200208%20Appendix%201))。現今在日文環境中 UTF-8 逐漸成為主流，不過 Shift-JIS 仍可在某些舊網站、電子郵件或檔案中遇到。
        
        在 .NET 中，`Shift-JIS` 對應的碼頁編號是 `932` (`Windows-31J` 是其超集)。若需處理日文舊系統資料，可能需要用到此編碼。
        

除了上述之外，還有例如 **ISO-8859 系列** (拉丁語系各國的單位元編碼) 以及其他國家 / 地區的本地編碼 (如**簡體中文**的 `GB2312`/`GBK`/`GB18030` 等)。但整體趨勢是 **Unicode**已成主流，建議在可能情況下都優先使用 `Unicode` 編碼 (UTF-8/UTF-16) 處理文字，以避免不同編碼轉換造成的困擾。

### 如何取得與使用編碼

在 .NET 中，可以透過多種方式取得對應的 `Encoding` 物件：

-   **靜態屬性**
    
    `Encoding` 類別直接提供了幾個常用編碼的靜態屬性，例如 `Encoding.UTF8`、`Encoding.Unicode`(UTF-16 LE)、`Encoding.BigEndianUnicode`(UTF-16 BE)、`Encoding.ASCII` 等，可直接使用這些屬性取得對應的編碼實例。
    
    這些屬性涵蓋了 ASCII 和主要的 Unicode 編碼，通常是最常用的選擇。
    
-   **類別建構函式**
    
    對於 ASCII、UTF7、UTF8、UTF16、UTF32 等，也可以直接呼叫相應的編碼類別的建構子來建立實例，例如 `new UTF8Encoding()`、`new UnicodeEncoding()` 等。
    
    靜態屬性其實已提供**單例實例**(Singleton)，一般不需要重複建立，但透過建構函式可以設定一些參數 (例如是否提供 `BOM` 或錯誤回退行為等)。
    
-   **GetEncoding 方法**
    
    `Encoding.GetEncoding(name或codePage)` 是一種更通用的取得編碼實例的方法。它允許你以**編碼名稱** (如 `"UTF-8"`, `"Big5"`, `"Shift_JIS"`, `"windows-1252"` 等) 或**碼頁編號** (code page，如 950、932 等) 來取得對應的編碼。例如：
    
    ```csharp
    Encoding utf8 = Encoding.GetEncoding("UTF-8");
    Encoding big5 = Encoding.GetEncoding("Big5");
    Encoding big5 = Encoding.GetEncoding(950);      // 950 是 Big5 碼頁
    Encoding sjis = Encoding.GetEncoding("Shift_JIS");
    ```
    
    然而，需要注意並非所有編碼在 .NET Core/.NET 5+ 中都**直接可用**。在 .NET Framework 上，`GetEncoding` 幾乎可以取得任何已安裝於 Windows 的碼頁編碼。但在 .NET Core 平台，出於精簡預設僅內建支援少數幾種編碼 (主要是 Unicode 系列和 latin-1 等)。
    
    例如：如果直接在 .NET Core 3.1 或 .NET 5 的程式中呼叫 `Encoding.GetEncoding("Big5")`，會拋出例外，訊息提示「不支援的編碼」或建議使用 `Encoding.RegisterProvider` 方法。這是因為 .NET Core 預設不包含 Big5、Shift-JIS 等傳統碼頁。
    
    ```csharp
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    ```
    
-   **Encoding.GetEncodings**
    
    如果想查看目前環境支援哪些編碼，可以呼叫 `Encoding.GetEncodings()` 列出所有可用的編碼資訊。
    
    但如上所述，在未額外設定下 .NET Core 列出的清單會相對有限。
    

#### 支援額外編碼 (安裝 CodePagesEncodingProvider)

為了在 .NET Core / .NET 5+ 中使用 Big5、Shift-JIS、Windows-125x 等額外的編碼，需要引入 **CodePagesEncodingProvider**。具體作法是：

1.  **安裝套件**
    
    在專案中安裝 NuGet 套件 **`System.Text.Encoding.CodePages`**。此套件包含了對各種碼頁的支援資料。
    
    > [How to read Windows-1252 encoded files with .NETCore and .NET5+ | Nicola Iarocci](https://nicolaiarocci.com/how-to-read-windows-1252-encoded-files-with-.netcore-and-.net5-/#:~:text=It%20turns%20out%20that%20under,System.Text.Encoding.CodePages)
    
2.  **註冊提供者**
    
    在程式啟動時 (例如 `Main` 方法或應用程式初始化時) 呼叫，只需要呼叫一次：
    
    ```csharp
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    ```
    
    這行程式會註冊一個**編碼提供者**([CodePagesEncodingProvider](https://learn.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider?view=net-9.0#:~:text=do%20the%20following%3A))，讓 .NET Core 能夠使用 .NET Framework 提供的那些傳統碼頁編碼。註冊後，先前不支援的編碼就會被加入可用清單中。
    
3.  **取得編碼**
    
    提供器註冊後，即可正常使用 `Encoding.GetEncoding()` 來取得所需的編碼物件，例如 `Encoding.GetEncoding("Big5")` 或 `Encoding.GetEncoding("Shift_JIS")`，不再拋出不支援例外。之後便可像使用其他 Encoding 一樣讀寫轉換文字。
    

下面是完整的範例流程：假設需要讀取 Big5 編碼的檔案，可這樣撰寫程式碼：

```csharp
using System.Text;

// 安裝了 System.Text.Encoding.CodePages 套件後：
using System.Text.Encoding.CodePages;

// ... 程式啟動時:
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

// 讀取 Big5 編碼的文字檔
Encoding big5 = Encoding.GetEncoding("Big5");
string content = File.ReadAllText("data_big5.txt", big5);
```

經過上述步驟，`Encoding` 類別就能支援大部分傳統編碼 (涵蓋各國碼頁)，這對需要處理舊檔案或與非 Unicode 系統相容性時非常有用 ([How to read Windows-1252 encoded files with .NETCore and .NET5+ | Nicola Iarocci](https://nicolaiarocci.com/how-to-read-windows-1252-encoded-files-with-.netcore-and-.net5-/#:~:text=And%20sure%20enough%2C%20all%20those,footprint%20as%20much%20as%20possible))。請注意，註冊動作只需要執行一次 (通常在應用程式啟動時)，一旦註冊，後續所有 `GetEncoding` 呼叫都能取得額外的編碼，不需要每次重複註冊提供者。

### 跨平台相容性

由於 .NET Core/.NET 5+ 是跨平台的運行時環境，在不同作業系統上的編碼支援略有差異，需要特別注意以下幾點：

-   **預設支援的編碼**
    
    正如前面所述，.NET Core 為減少體積僅內建支援少數編碼，除了 Unicode 家族 (UTF-8/16/32) 和 ASCII 以及 ISO-8859-1 (碼頁 28591) 外，其他如 Big5、Shift-JIS、Windows-1252 等都不內建。相對地，傳統 .NET Framework (僅限 Windows) 則憑藉作業系統提供了全面的碼頁支援。
    
    因此，如果一段程式碼在 .NET Framework 上可以直接使用 `Encoding.GetEncoding(1252)` 取得 `Windows-1252` 編碼，在 .NET Core 上執行則需要先安裝套件並註冊提供者，否則會報錯「沒有可用的編碼」。
    
-   **Encoding.Default**
    
    在 .NET Framework 上，`Encoding.Default` 屬性會返回當前系統的 `ANSI` 編碼，例如美國英文系統通常是 `Windows-1252`，繁體中文系統則會是 `Big5`，依據作業系統的**地區設定**而定。
    
    但在 .NET Core/5+ 中，為了跨平台一致性，`Encoding.Default` 被固定定義為 UTF-8 編碼。也就是說，在 Linux、macOS 或 Windows 上，.NET Core 的 `Default` 都是 `UTF-8`，而不再是各自系統的本地碼頁，這點差異必須特別注意。
    
    因此，**不要依賴** `Encoding.Default` 來猜測**本地編碼**，因為在新平台下它並不代表使用者環境的語系；若需要特定編碼，應明確指定。例如，以下程式在 .NET Framework 和 .NET 6 上會有不同結果：
    
    ```csharp
    Console.WriteLine(Encoding.Default.BodyName);
    ```
    
    在 .NET Framework (假設系統地區為台灣) 可能輸出 `Big5`，但在 .NET 6 不論系統語言都會輸出 `utf-8`。這項差異提醒我們，在跨平台程式中**最好自行指定編碼**，而非使用預設值。
    
-   **不同 OS 的編碼行為**
    
    由於各作業系統底層對編碼支援不同，某些情境下也要注意。例如在 Linux 上跑 .NET，如果沒有透過 `CodePagesEncodingProvider`，引入的碼頁資料其實來自 .NET 的內建對照表，而非作業系統。
    
    所以**一旦註冊了 CodePagesEncodingProvider，編碼行為在各平台應該是一致的** —— 因為都使用相同的對應表。
    
    不過仍建議盡量使用 Unicode 等通用編碼來進行跨平台文字交換，因為這樣最簡單也最不易出錯。如果不得不使用如 `Big5` 等區域性編碼，也要確保所有執行環境都有安裝相應提供器，並在測試時於不同 OS 上驗證文字顯示正確無誤。
    

總之，在開發跨平台 .NET 應用時，應養成明確指定編碼的習慣，並利用 Unicode 編碼來統一處理資料，只有在需要相容時才使用特定碼頁且正確載入支援。這能最大程度確保 Windows、Linux、Mac 上輸入輸出文字的一致性。

### 實作範例

下面透過幾個常見的場景，說明如何正確使用 `Encoding` 類別來讀寫資料，避免發生亂碼或編碼錯誤。

-   讀取與寫入文字檔案
    
    日常開發中經常需要讀寫文字檔，正確處理編碼能避免產生亂碼的問題。例如，我們有一個文字檔，其實內容是以 Big5 編碼儲存的繁體中文，但若程式錯誤地以 UTF-8 去解讀它，就可能出現「鬼畫符」般的亂碼。
    
    ![Image](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/1011e44c9bc0b9b53632-7b9b3f3c-8aab-4d73-99e9-74bac738a83f.png)
    
    如上圖所示，當以錯誤的編碼解讀文字時，會出現無意義的亂碼。展示了原本以 UTF-8 編碼的日文維基百科頁面，如果錯用 `Windows-1252` 編碼去解讀時的情況 —— 文本變成一連串毫無意義的符號。類似地，如果讀取檔案時編碼搞錯，也會得到亂七八糟的內容甚至問號。同理，寫出檔案時若編碼不對，其他應用開啟時也可能是亂碼。
    
    **避免亂碼的關鍵**在於**讀寫雙方**約定使用相同的編碼。例如，若知道檔案是 Big5 編碼，就要用 Big5 去讀取。在 .NET 中，可以使用 `File.ReadAllText`/`File.WriteAllText` 等方法並指定編碼參數，或使用 `StreamReader`/`StreamWriter` 來完成。例如：
    
    ```csharp
    // 讀取 Big5 編碼的檔案
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 確保支援Big5
    
    string txt = File.ReadAllText("article.txt", Encoding.GetEncoding("Big5"));
    
    // 寫出 UTF-8 編碼的檔案
    string output = "你好，Hello!";
    File.WriteAllText("output.txt", output, Encoding.UTF8);
    ```
    
    上例中，我們在讀取前註冊了編碼提供器以支援 Big5，然後明確指定使用 Big5 去解碼檔案內容。寫出檔案時則指定以 UTF-8 編碼輸出字串。如此一來，就不會發生亂碼問題。
    
    使用 `StreamReader`/`StreamWriter` 時情況類似：
    
    ```csharp
    // 使用 StreamReader 讀取 (假設檔案為 Shift_JIS 編碼)
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    using var reader = new StreamReader("data_sjis.txt", Encoding.GetEncoding("Shift_JIS"));
    string line;
    while((line = reader.ReadLine()) != null) {
        // 處理每行文字
    }
    
    // 使用 StreamWriter 寫入 (例如寫入 UTF-16 編碼檔案)
    using var writer = new StreamWriter("log_unicode.txt", false, Encoding.Unicode);
    writer.WriteLine("記錄一條日誌...");
    ```
    
    特別地，`StreamReader` 在**未指定編碼**時，.NET Core 中預設會以 UTF-8 嘗試解碼 (並自動偵測 UTF-8/UTF-16 的 BOM)。如果檔案本身不是 UTF-8，這時就可能解讀錯誤。因此對於沒有 BOM 且非 UTF-8 編碼的檔案，更要明確指定正確的編碼。總之，**讀寫檔案最好始終指定編碼**，以免依賴預設值而發生預料外的結果。
    
-   網路請求與回應 (API)
    
    在進行 HTTP 通訊或呼叫 API 時，也經常涉及文字的編碼。例如發送一個 JSON 請求，預設應使用 UTF-8 編碼內容；又或者請求一個網頁，需根據對方提供的編碼來解碼回應。
    
    **請求方面**：使用 .NET 的 `HttpClient` 時，可以透過 `StringContent` 指定內容的編碼和媒體類型。例如：
    
    ```csharp
    var json = "{\"name\":\"測試\"}";
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    await httpClient.PostAsync(apiUrl, content);
    ```
    
    上述程式將確保發出的 HTTP 請求主體使用 UTF-8 編碼，並告知伺服器內容類型為 JSON (包含 charset=UTF-8)。大部分網路 API 都預設採用 UTF-8 傳輸文字資料，如果需要其他編碼 (較少見)，也應在此明確指定，例如：
    
    ```csharp
    var json = "{\"name\":\"測試\"}";
    var content = new StringContent(json, Encoding.GetEncoding("Big5"), "application/json");
    await httpClient.PostAsync(apiUrl, content);
    ```
    
    **回應方面**：接收資料時要依 Content-Type 的字元編碼來解碼。比如使用 `HttpClient` 時，若知道回應是 UTF-8，可直接用 `response.Content.ReadAsStringAsync()` 取得字串 (內部會據 content-type 的 charset 解碼)。但如果回應是其他編碼 (假設一個老舊服務回傳 Big5 編碼的 HTML)，可以這樣處理：
    
    ```csharp
    byte[] bytes = await httpClient.GetByteArrayAsync(url);
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    string html = Encoding.GetEncoding("Big5").GetString(bytes);
    ```
    
    先取得**位元組陣列**(`byte[]`)，再用正確的編碼解碼成字串。
    
    針對使用 ASP.NET 建構的 Web 應用，框架本身也提供編碼設定的屬性。例如可以將 `HttpResponse.ContentEncoding` 設定為與請求相同的編碼，確保送回給用戶端的頁面使用的是它可以正確顯示的編碼。
    
    通常情況下，目前瀏覽器與服務端通訊都以 UTF-8 為主，但如果你的網站需要支持特定語系 (如繁中 zh-TW 頁面)，務必在 Response 設定對應的 Encoding，並在 HTML `<meta charset>` 或 HTTP Header 中宣告編碼，讓瀏覽器正確渲染。
    
-   資料庫存取
    
    資料庫的文字處理雖然大多對開發者透明，但仍有一些最佳做法來避免編碼問題。現代關聯式資料庫 (如 SQL Server、MySQL、PostgreSQL 等) 通常都支援 Unicode 欄位類型 (例如 `NVARCHAR`/`NCHAR` 等類型)，建議優先使用這些欄位儲存文字，以便資料庫和 .NET 應用之間以 Unicode 字串直接交換資料，而不需考慮中間編碼轉換。
    
    如果你的資料庫欄位使用非 Unicode 編碼 (例如某歷史系統的資料庫使用 Big5 編碼儲存文字)，那在存取時就要特別小心**一致性**。一般透過 ADO.NET 提供者 (例如 System.Data.SqlClient 或 MySQL Connector 等) 查詢資料時，驅動程式會參考**連線字元集**或**資料庫的編碼**設定，自動將資料轉為 .NET 的 Unicode 字串。然而前提是**連線字串**或**資料庫屬性**要正確配置。例如 MySQL 連線字串通常需要指定 `Charset=utf8` 或其他編碼，否則可能出現亂碼。
    
    在撰寫程式時，儘量讓資料庫傳回 `string` 而非 `byte[]` 來避免手動解碼。如果不得已需要從資料庫取得位元組再轉字串，一定要知道那些位元組代表的編碼。例如：
    
    ```csharp
    // dataReader 是從資料庫讀取出來的 DataReader
    byte[] blob = (byte[])dataReader["Name_Bytes"];
    string name = Encoding.GetEncoding("Big5").GetString(blob);
    ```
    
    如上，明確以 Big5 解碼才不會造成誤解碼。如果編碼不對，可能出現亂碼或問號。寫入時亦同理，應將字串轉成正確編碼的位元組再存入。如果資料庫與應用都使用 UTF-8/Unicode，則這方面的煩惱最少。因此**最好的辦法還是保持端到端皆使用 Unicode**，在資料庫層面就避免資訊遺失的風險。
    

#### 跨平台文字處理

在跨平台環境下 (例如資料在 Windows 上產生，拿到 Linux 上處理)，更需要小心編碼的一致。建議遵循以下原則：

-   **使用通用編碼交換資料**
    
    如果可能，儘量使用 UTF-8 編碼來儲存和交換文字資料，因為它在各平台都是標準支援的 (.NET Core 預設就是 UTF-8)。
    
    例如，你在 Windows 上產生一個 UTF-8 編碼的檔案，那麼在 Linux 或 macOS 上用 .NET 讀取時，不用做任何特殊設定就能正確解碼。
    
-   **夾帶編碼訊息**
    
    在需要傳輸文字的場合，最好能夠隨資料附帶編碼說明。例如 XML/HTML 檔案應在檔頭宣告 encoding，或純文字檔可以考慮加上 `BOM` ([Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark)) 來提示。
    
    雖然 `BOM` 不是強制的，但對於 UTF-8/UTF-16 檔案，Windows 上的某些軟體 (如舊版記事本) 會依賴 BOM 判斷編碼。如果沒有 BOM，確保協定或文件格式有其他方式知會編碼，否則另一端可能用錯編碼打開導致亂碼。
    
-   **碼頁編碼的跨平台**
    
    如果必須交換非 Unicode 編碼的資料 (例如老系統產生了 Big5 檔案，要在 Linux .NET 上處理)，務必在 .NET Core 環境安裝 `System.Text.Encoding.CodePages` 並 `RegisterProvider`，否則程式無法識別 Big5 編碼。
    
    好在只要提供器註冊了，在 Linux 下處理 Big5 與在 Windows 下效果是一樣的。因此，只要我們在跨平台程式中明確指定使用何種編碼讀寫，就能確保不同系統下結果相同。
    

簡而言之，跨平台時將文字以 Unicode (如 UTF-8) 形式交流是最穩妥的做法。如果必須使用其他編碼，也要讓接收方清楚知道使用何種編碼並具備相應支援。保持這種約定一致，才能避免「平台 A 寫的文件到平台 B 變亂碼」的窘境。

#### 串流處理 (Stream)

除了檔案與網路，應用程式內也可能需要在串流中讀寫文字，例如讀寫 `MemoryStream`、`NetworkStream` 等。使用 `StreamReader` 和 `StreamWriter` 配合 Encoding 可以方便地處理串流中的文字內容。用法上與檔案讀寫類似，只是改為對接任意的 Stream。

例如，假設有一段程式要經由網路串流傳送指令，接收端約定用 ASCII 編碼解析，那發送端可以這樣寫：

```csharp
using NetworkStream ns = tcpClient.GetStream();
using StreamWriter writer = new StreamWriter(ns, Encoding.ASCII);
writer.Write("HELLO\n");
writer.Flush();
```

這會將字串「HELLO」按 ASCII 編碼寫入網路串流。接收端只要也按 ASCII 讀取就能得到正確的字元。再如，接收端想讀取 UTF-8 的訊息：

```csharp
using StreamReader reader = new StreamReader(ns, Encoding.UTF8);
string message = reader.ReadLine();
```

`StreamReader`/`Writer` 建構子允許我們指定編碼，還能指定是否需要偵測 BOM、緩衝大小等。若未指定編碼，`StreamReader` 預設行為與前述檔案情況一樣會當作 UTF-8 (含 BOM 偵測)。所以除非可以確定串流資料一定是 UTF-8，否則應明確給出編碼參數。

處理串流時，最好也考慮**緩衝與 Flush** 問題，例如上例中在寫入後呼叫了 `Flush()`，確保資料立即從緩衝送出。另外，如果串流是雙向或長連線的，通信雙方應先約定好使用何種文字編碼來解析彼此的訊息，必要時可在協定中增加檔頭來協調 (類似 HTTP 的 Content-Type)。總之原則和先前一致：雙方編碼設定相符，傳輸內容才能正確還原。

### 最佳實踐與建議

綜合以上內容，以下是關於文字編碼在 .NET Core (含 .NET 5+) 開發中的一些最佳實踐建議：

-   **預設使用 Unicode 編碼**
    
    優先選擇 UTF-8 或 UTF-16 作為應用程式處理和儲存文字的編碼。UTF-8 對英文內容幾乎沒有體積負擔，對全球語言都有支援，而且效能非常高。除非有特殊需求，UTF-8 通常是讀寫檔案、網路通訊的首選編碼。UTF-16 在 .NET 字串內部使用，可在需要固定雙位元組 (例如與某些 Windows API 互動) 時考慮。不建議使用 UTF-7 (已淘汰，安全性差) 等已過時的編碼。
    
-   **減少不必要的編碼轉換**
    
    既然 .NET 字串本身就是 Unicode，應儘量讓資料以 Unicode 形式進出系統，避免在不同編碼之間來回轉換。比如從資料庫讀出 UTF-8 bytes -> 轉字串 -> 再轉成另一編碼 bytes 這種流程能免則免，直接在資料庫使用 Unicode 欄位即可省去轉換環節，減少出錯機率和效能開銷。
    
-   **謹慎使用 ASCII/Ansi 編碼**
    
    僅在明確確認資料範圍時才使用 ASCII 或區域性碼頁編碼。例如只包含英數字元的通訊協定可使用 ASCII，但如果稍有可能出現非 ASCII 字元，應改用 UTF-8 以免資訊遺失。
    
    實務中常見錯誤是誤用 ASCII 導致非英文字元全部變成問號 `?`。同理，除非處理特定舊系統資料，否則很少需要硬性使用 Big5、Shift-JIS 等編碼；即便需要，也應全程測試確保沒有字元轉換錯誤。
    
-   **永遠明確指定編碼**
    
    無論是讀檔、寫檔，還是串流通訊，**都養成指定 Encoding 的習慣**，不要依賴預設值。這在跨區域、跨平台時尤為重要。例如 `File.ReadAllText(path)` 在 .NET Framework 上會用系統預設 ANSI 編碼讀檔案，在 .NET Core 則會用 UTF-8，行為不一致，而使用 `File.ReadAllText(path, Encoding.X)` 則可避免這種差異。
    
    同樣，`StreamReader/Writer` 請提供 Encoding 參數。總之，**將編碼視為檔案格式/通訊協定的一部分**，跟檔名、副檔名一樣需要被明確管理。
    
-   **啟用額外編碼提供器**
    
    對於需要處理非 Unicode 編碼資料的應用，例如批次轉換老舊文件編碼、讀取舊系統輸出報表等，請務必在程式初始化時呼叫 `Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)` 並隨專案一起部署 `System.Text.Encoding.CodePages` 套件。這樣可以避免某些環境下 `GetEncoding` 取不到所需編碼的問題。
    
    一旦註冊，一些碼頁編碼就能跨平台使用，如 Big5 在 Linux 上也能正確轉換。
    
-   **注意 Fallback (後備機制)**
    
    `Encoding` 在編碼 / 解碼遇到無法對應的字元時，預設會以「`?`」取代未知字元。雖然這可避免拋出錯誤中斷程式，但有時可能掩蓋資料問題。如果對資料完整性要求很高，可以考慮設定 EncoderFallback/DecoderFallback 為 `ExceptionFallback`，使其在遇到無法處理的字元時拋出例外，方便偵錯。此外也可客製替代字元。但大多數情況下，只要源和目標編碼對應正確，不應該出現亂碼或無法對應的情況。
    
-   **測試與文件**
    
    最後，針對編碼相關的功能，多在不同語言環境下測試 (例如切換系統語言或在不同 OS 上測試)，以發現潛在的問題。並且在文件或程式碼註解中標明資料的編碼假設，方便日後維護者了解。舉例而言，若你的應用匯出一份報表檔案是 Big5 編碼，務必在說明檔提及，否則接手的人可能困惑為何不用 UTF-8。
    

總而言之，文字編碼雖然看不見但無處不在。良好的習慣是：**盡可能以 Unicode 處理文字，全程保持編碼一致**，在需要轉換時使用 .NET 提供的 `Encoding` 工具類別並小心核對。

遵循這些最佳實踐，可以大幅降低遇到亂碼的機率，讓你的 .NET 應用在任何地區任何平台都能正確地讀寫文字資料。

#### 相關連結

-   [How to use character encoding classes in .NET - .NET | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-encoding?WT.mc_id=DT-MVP-4015686)
-   [Correctly reading encoded text with the StreamReader in .NET | Jeremy Lindsay](https://jeremylindsayni.wordpress.com/2019/01/23/correctly-reading-encoded-text-with-the-streamreader-in-net/#:~:text=,Unicode%20or%20code%20page%E2%80%93based%20applications.%E2%80%9D)
-   [Correctly reading encoded text with the StreamReader in .NET | Jeremy Lindsay](https://jeremylindsayni.wordpress.com/2019/01/23/correctly-reading-encoded-text-with-the-streamreader-in-net/#:~:text=System,16)
-   [How to read Windows-1252 encoded files with .NETCore and .NET5+ | Nicola Iarocci](https://nicolaiarocci.com/how-to-read-windows-1252-encoded-files-with-.netcore-and-.net5-/#:~:text=And%20sure%20enough%2C%20all%20those,footprint%20as%20much%20as%20possible)
