比較 Word 文件 C# 使用 .NET 中的串流比較

簡介

如果您需要在 .NET 應用程式中 compare word documents c#,且希望保持低記憶體使用量,您來對地方了。傳統的檔案基礎比較會將整個文件載入 RAM,對於大型 Word 檔或僅有串流的雲端原生情境會迅速成為瓶頸。本教學將一步步示範如何使用 GroupDocs.Comparison 進行串流式文件比較,並提供實務範例、效能技巧與除錯建議。

快速答案

  • 什麼程式庫處理串流比較? GroupDocs.Comparison for .NET。
  • 我可以直接從 MemoryStream 比較 Word 檔案嗎? 是 – 只需將串流傳遞給比較器。
  • 生產環境需要授權嗎? 絕對需要;有效的 GroupDocs.Comparison 授權會移除浮水印。
  • 支援哪些 .NET 版本? .NET Framework 4.6.1+、.NET Core 2.0+、.NET 5/6/7。
  • 是否內建 async 支援? 原生未支援,但可將呼叫包在 Task.Run 中以取得基本的非同步行為。

什麼是基於串流的文件比較?

GroupDocs.Comparison 中的 Comparer 類別可從任何 Stream 實作讀取文件資料,讓比較過程不必將檔案寫入磁碟。這使其非常適合雲端儲存、大檔案處理與高併發的 Web 服務。

為什麼在 C# 中使用基於串流的比較來比較 Word 文件?

基於串流的比較透過分塊處理資料,減少記憶體壓力,而非一次載入整個檔案。GroupDocs.Comparison 支援 50+ 輸入與輸出格式——包括 DOCX、PDF、PPTX、XLSX——且能處理上百頁的文件而不耗盡伺服器 RAM。此方式亦與 Azure Blob、AWS S3 或任何以 Stream 而非實體檔案路徑提供資料的 HTTP 儲存完美契合。

先決條件

  • GroupDocs.Comparison for .NET(版本 25.4.0 或更新)– 支援 50 多種格式。
  • .NET Framework 4.6.1+ .NET Core 2.0+(包括 .NET 5/6/7)。
  • 具備 C# 支援的 IDE(Visual Studio、VS Code 或 Rider)。
  • 基本的 C# 串流知識(FileStreamMemoryStream)與 using 陳述式。

設定 GroupDocs.Comparison for .NET

安裝步驟

使用 NuGet 套件管理員主控台

Install-Package GroupDocs.Comparison -Version 25.4.0
Install-Package GroupDocs.Comparison -Version 25.4.0

使用 .NET CLI

dotnet add package GroupDocs.Comparison --version 25.4.0
dotnet add package GroupDocs.Comparison --version 25.4.0

專業提示: 鎖定版本號以避免在新主要版本發布時出現意外的破壞性變更。

授權設定(重要!)

GroupDocs.Comparison 需要授權才能在生產環境使用。您可以先使用免費試用、取得臨時授權以進行概念驗證,或購買正式授權以無限制部署。前往 GroupDocs Purchase 了解詳情。

基本授權初始化

var license = new GroupDocs.Comparison.License();
license.SetLicense("GroupDocs.Comparison.lic");
using GroupDocs.Comparison;
using System.IO;

// This is your foundation for all comparisons
Comparer comparer = new Comparer();

現在您已準備好從任何串流來源比較文件。

如何使用串流比較 Word 文件(C#)?

將來源與目標 Word 檔案載入為串流,傳給 Comparer,再將結果寫入輸出串流。完整流程如下圖所示。

步驟 1:準備來源、目標與輸出串流

using (var sourceStream = File.OpenRead("Original.docx"))
using (var targetStream = File.OpenRead("Revised.docx"))
using (var resultStream = File.Create("ComparisonResult.docx"))
{
    // Comparison logic goes here
}
string sourceDocumentPath = Path.Combine("YOUR_DOCUMENT_DIRECTORY", "source.docx");
string targetDocumentPath = Path.Combine("YOUR_DOCUMENT_DIRECTORY", "target.docx");
string outputDirectory = Path.Combine("YOUR_OUTPUT_DIRECTORY", ".");
string outputFileName = Path.Combine(outputDirectory, "comparison_result.docx");

using (Comparer comparer = new Comparer(File.OpenRead(sourceDocumentPath)))
{
    // Step 2: Add the Target Document
    comparer.Add(File.OpenRead(targetDocumentPath));

    // Step 3: Perform Comparison and Save Results
    comparer.Compare(File.Create(outputFileName));
}

說明:

  • File.OpenRead 為兩個 Word 檔案建立唯讀串流。
  • File.Create 開啟唯寫串流,用於儲存比較結果。
  • using 陳述式保證每個串流在區塊結束時即被釋放,防止檔案鎖定與記憶體泄漏。

步驟 2:以來源串流初始化 Comparer

var comparer = new GroupDocs.Comparison.Comparer(sourceStream);
// Example: Comparing documents from byte arrays
byte[] sourceBytes = GetDocumentFromDatabase(sourceId);
byte[] targetBytes = GetDocumentFromDatabase(targetId);

using (var sourceStream = new MemoryStream(sourceBytes))
using (var targetStream = new MemoryStream(targetBytes))
using (var outputStream = new MemoryStream())
using (var comparer = new Comparer(sourceStream))
{
    comparer.Add(targetStream);
    comparer.Compare(outputStream);
    
    // Now you can work with the result in memory
    byte[] resultBytes = outputStream.ToArray();
}

定義說明: Comparer 類別是 GroupDocs.Comparison 的核心元件,負責載入、分析並產生兩個或多個文件串流之間的差異。

步驟 3:加入目標串流

comparer.Add(targetStream);
// If you must reuse a stream, reset its position
stream.Position = 0;

您可以多次呼叫 Add,在一次執行中將來源與多個目標版本進行比較。

步驟 4:執行比較並寫入結果

ComparisonResult 代表比較的結果,包含差異文件與相關的中繼資料。

var result = comparer.Compare();
result.Save(resultStream);
// Good - automatic disposal
using (var stream = File.OpenRead(path))
{
    // Use stream
}

// Also good - manual disposal
var stream = File.OpenRead(path);
try
{
    // Use stream
}
finally
{
    stream?.Dispose();
}

發生了什麼?

  • Compare() 處理兩個串流,偵測插入、刪除與格式變更,並回傳 ComparisonResult 物件。
  • Save() 將帶有標示的比較文件寫入先前建立的 resultStream

進階串流處理

使用 MemoryStream(例如透過 HTTP 上傳的檔案)

當您的應用程式收到檔案上傳時,通常會取得 MemoryStream。相同的 API 可直接使用,無需修改:

var uploadedSource = new MemoryStream(await httpRequest.Form.Files[0].OpenReadStream().ReadAllBytesAsync());
var uploadedTarget = new MemoryStream(await httpRequest.Form.Files[1].OpenReadStream().ReadAllBytesAsync());

var comparer = new GroupDocs.Comparison.Comparer(uploadedSource);
comparer.Add(uploadedTarget);
var result = comparer.Compare();

await result.SaveAsync(response.Body);
if (stream.CanSeek)
{
    // Safe to use Position and Length properties
}

為什麼重要: 使用 MemoryStream 可免除在磁碟上建立暫存檔的需求,提升無狀態 Web 服務與容器化環境的效能。

常見陷阱與解決方案

陷阱 #1:串流位置未重設

問題: 若串流先前已被讀取(例如驗證),其位置可能已在結尾,導致比較器讀取到零位元組。
解決方案: 在傳遞串流前重設位置:

sourceStream.Position = 0;
targetStream.Position = 0;
// Example of async file reading (though GroupDocs.Comparison doesn't support async yet)
var sourceBytes = await File.ReadAllBytesAsync(sourcePath);
using (var sourceStream = new MemoryStream(sourceBytes))
{
    // Comparison logic
}

陷阱 #2:忘記釋放串流

問題: 未釋放的串流會保持檔案句柄開啟,導致「檔案正在使用」錯誤。
解決方案: 總是將串流包在 using 區塊內,或如核心實作所示明確呼叫 Dispose()

陷阱 #3:使用不可定位的串流

問題: 某些網路串流(例如 NetworkStream)不支援定位,而比較器可能需要此功能。
解決方案: 先將不可定位的串流複製到 MemoryStream

var seekableStream = new MemoryStream();
await nonSeekableStream.CopyToAsync(seekableStream);
seekableStream.Position = 0;
[HttpPost]
public async Task<IActionResult> CompareDocuments(IFormFile sourceFile, IFormFile targetFile)
{
    using (var sourceStream = sourceFile.OpenReadStream())
    using (var targetStream = targetFile.OpenReadStream())
    using (var outputStream = new MemoryStream())
    using (var comparer = new Comparer(sourceStream))
    {
        comparer.Add(targetStream);
        comparer.Compare(outputStream);
        
        return File(outputStream.ToArray(), "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "comparison.docx");
    }
}

效能最佳實踐

最佳化記憶體使用

  • 緩衝區大小調整: 對於大於 50 MB 的文件,將內部緩衝區大小提升至 1 MB,以減少讀寫循環。
  • 非同步 I/O: 在 ASP.NET Core 中,使用非同步檔案 API(FileStream.OpenReadAsync)以在 I/O 時釋放執行緒。

監控資源消耗

  • 效能計數器: 在比較前後追蹤 Process.PrivateMemorySize64 以驗證記憶體影響。
  • 效能測試: 執行 dotnet benchmark 測試,比較基於檔案與基於串流的方法;在 200 頁 DOCX 文件上,串流方式通常快 20‑30%。

併發控制

  • 佇列系統: 限制同時比較的數量至 CPU 核心數,以避免記憶體不足的崩潰。
  • 提前釋放:Compare() 回傳後立即釋放來源與目標串流;結果串流可保留開啟狀態,直至寫入客戶端。

實務案例

案例 1:Web 應用程式文件審閱

一個 SaaS 平台允許使用者上傳兩個合約版本進行並排審閱。上傳的檔案以 IFormFile 物件形式取得,轉換為 MemoryStream 後即時比較,並回傳帶有變更追蹤的可下載 DOCX。

案例 2:從雲端儲存批次處理

Azure Function 監聽容器中新上傳的 Blob,將每個 Blob 以串流讀取,與另一容器中儲存的基準版本比較,最後將比較結果寫回「results」容器。

案例 3:版本控制整合

DevOps 管線從 Git 倉庫抽取 Word 檔案,將它們串流送入 GroupDocs.Comparison,產生差異報告,並將報告附加於建置產物供稽核使用。

故障排除指南

問題可能原因解決方式
「Stream 不支援讀取」傳入唯寫串流(例如 File.OpenWrite使用 File.OpenRead 或確保 CanRead 為 true。
「物件參考未設定為物件的執行個體」比較前串流為 null 或已被釋放確認串流已正確初始化,並在 Compare() 之後仍保持 using 區塊開啟。
「100 MB 以上檔案效能不佳」預設緩衝區大小太小,或同時執行的任務過多增大緩衝區大小、限制併發,並使用 dotMemory 進行效能分析。
「生產環境授權錯誤」授權檔案遺失或路徑不正確GroupDocs.Comparison.lic 放置於應用程式根目錄,並在啟動時盡早呼叫 SetLicense
「串流資料損毀」從雲端儲存下載時網路中斷在比較前驗證串流長度與檢查碼。

進階設定選項

var options = new CompareOptions
{
    HighlightColor = Color.Yellow,
    ShowDeletedContent = true,
    ShowInsertedContent = true,
    StyleChangeDetection = true,
    Password = "optionalPassword"
};
var comparer = new GroupDocs.Comparison.Comparer(sourceStream, options);
public async Task<byte[]> CompareCloudDocuments(string sourceUrl, string targetUrl)
{
    using (var httpClient = new HttpClient())
    using (var sourceStream = await httpClient.GetStreamAsync(sourceUrl))
    using (var targetStream = await httpClient.GetStreamAsync(targetUrl))
    using (var outputStream = new MemoryStream())
    using (var comparer = new Comparer(sourceStream))
    {
        comparer.Add(targetStream);
        comparer.Compare(outputStream);
        return outputStream.ToArray();
    }
}

定義說明: CompareOptions 是一個設定物件,可讓您控制視覺樣式、密碼保護以及報告的變更類型。

與流行 .NET 框架的整合

ASP.NET Core 整合

public async Task<IActionResult> Compare(IFormFile source, IFormFile target)
{
    using var sourceStream = new MemoryStream();
    using var targetStream = new MemoryStream();
    await source.CopyToAsync(sourceStream);
    await target.CopyToAsync(targetStream);
    sourceStream.Position = 0;
    targetStream.Position = 0;

    var comparer = new GroupDocs.Comparison.Comparer(sourceStream);
    comparer.Add(targetStream);
    var result = comparer.Compare();

    var output = new MemoryStream();
    result.Save(output);
    output.Position = 0;
    return File(output, "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                "ComparisonResult.docx");
}
public DocumentComparisonResult CompareDocumentVersions(int documentId, int version1, int version2)
{
    var doc1Stream = GetDocumentVersionStream(documentId, version1);
    var doc2Stream = GetDocumentVersionStream(documentId, version2);
    
    using (doc1Stream)
    using (doc2Stream)
    using (var outputStream = new MemoryStream())
    using (var comparer = new Comparer(doc1Stream))
    {
        comparer.Add(doc2Stream);
        comparer.Compare(outputStream);
        
        return new DocumentComparisonResult
        {
            ComparisonData = outputStream.ToArray(),
            ComparedAt = DateTime.UtcNow,
            SourceVersion = version1,
            TargetVersion = version2
        };
    }
}

Windows Forms / WPF 整合

var openFileDialog = new OpenFileDialog { Filter = "Word files (*.docx)|*.docx" };
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
    using var source = File.OpenRead(openFileDialog.FileName);
    // Repeat for target, then compare as shown earlier
}
using (var comparer = new Comparer(sourceStream))
{
    comparer.Add(targetStream);
    
    var compareOptions = new CompareOptions
    {
        ShowDeletedContent = true,
        ShowInsertedContent = true,
        GenerateSummaryPage = true
    };
    
    comparer.Compare(outputStream, compareOptions);
}

結論

在 .NET 中使用基於串流的文件比較,可提供 記憶體效率高、雲端就緒且高效能 的方式來比較 Word 檔案。透過 GroupDocs.Comparison 的 Comparer 類別,您可以直接操作 Stream 物件,避免暫存檔,並可擴展至千級同時比較。遵循上述最佳實踐——正確釋放串流、調整緩衝區、妥善授權——即可確保生產環境的穩定實作。

資源


最後更新: 2026-05-31
測試環境: GroupDocs.Comparison 25.4.0 for .NET
作者: GroupDocs


public class DocumentComparisonService
{
    public async Task<ComparisonResult> CompareDocumentsAsync(Stream source, Stream target)
    {
        // Your comparison logic here
        // This is where the earlier examples would fit
    }
}
private void CompareButton_Click(object sender, EventArgs e)
{
    using (var openFileDialog = new OpenFileDialog())
    {
        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            using (var stream = File.OpenRead(openFileDialog.FileName))
            {
                // Perform comparison
            }
        }
    }
}

相關教學