.NET におけるストリーム比較を使用した C# の Word ドキュメント比較

はじめに

.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。
  • 非同期サポートは組み込みですか? 組み込みではありませんが、Task.Run で呼び出しをラップすれば基本的な非同期動作が可能です。

ストリームベースのドキュメント比較とは?

GroupDocs.Comparison の Comparer クラスは任意の Stream 実装からドキュメントデータを読み取り、ディスクに書き出すことなく比較を実行します。これにより、クラウドストレージや大容量ファイル処理、高並行性 Web サービスに最適なアプローチが実現します。

Word ドキュメント C# を比較するためにストリームベース比較を使用する理由

ストリームベース比較は、ファイル全体を読み込むのではなくチャンク単位で処理するためメモリ圧迫を軽減します。GroupDocs.Comparison は 50 以上の入力・出力フォーマット(DOCX、PDF、PPTX、XLSX など)をサポートし、サーバー RAM を使い果たすことなく数百ページのドキュメントを処理できます。また、Azure Blob、AWS S3、HTTP ベースのストレージから取得した Stream と直接連携でき、物理パスが不要です。

前提条件

  • 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 の設定

インストール手順

Using NuGet Package Manager Console

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

Using .NET CLI

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

プロのコツ: 新しいメジャーリリースが出たときに予期せぬ破壊的変更を防ぐため、バージョン番号を固定してください。

ライセンス設定(重要!)

GroupDocs.Comparison は本番利用にライセンスが必要です。無料トライアル、概念実証用の一時ライセンス、または無制限デプロイ向けのフルライセンスを取得できます。詳細は GroupDocs Purchase をご覧ください。

Basic License Initialization

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();

これで任意のストリームソースからドキュメントを比較できるようになりました。

ストリームを使用して C# で Word ドキュメントを比較する方法

ソースとターゲットの Word ファイルをストリームとして読み込み、Comparer に渡し、結果を出力ストリームに書き込みます。全体のフローは以下の通りです。

Step 1: Prepare Source, Target, and Output Streams

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));
}

Explanation:

  • File.OpenRead は 2 つの Word ファイル用の読み取り専用ストリームを作成します。
  • File.Create は比較結果を保存する書き込み専用ストリームを開きます。
  • using 文によりブロック終了時に各ストリームが即座に破棄され、ファイルロックやメモリリークを防止します。

Step 2: Initialize the Comparer with the Source Stream

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();
}

Definition anchor: Comparer クラスは GroupDocs.Comparison の中核コンポーネントで、複数のドキュメントストリームの読み込み・解析・差分生成を統括します。

Step 3: Add Target Stream(s)

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

Add を複数回呼び出すことで、ソースを複数のターゲットバージョンと一括比較できます。

Step 4: Execute Comparison and Write Result

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();
}

What happens here?

  • Compare() が両ストリームを処理し、挿入・削除・書式変更を検出して ComparisonResult オブジェクトを返します。
  • Save() がハイライトされた比較ドキュメントを、先に作成した resultStream に書き込みます。

高度なストリーム処理

Working with MemoryStream (e.g., files uploaded via 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
}

Why this matters: MemoryStream を使用するとディスク上の一時ファイルが不要になるため、ステートレスな Web サービスやコンテナ環境でのパフォーマンスが向上します。

よくある落とし穴と解決策

Pitfall #1: Stream Position Not Reset

Problem: 以前にストリームを読み取っている(例: バリデーション)と、位置が末尾に残り、コンパレーラが 0 バイトしか読めません。
Solution: ストリームを渡す前に位置をリセットします。

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
}

Pitfall #2: Forgetting to Dispose Streams

Problem: ストリームを破棄しないとファイルハンドルが残り、「ファイルが使用中」エラーが発生します。
Solution: 常に using ブロックでラップするか、明示的に Dispose() を呼び出してください(コア実装参照)。

Pitfall #3: Using Non‑Seekable Streams

Problem: NetworkStream など一部のネットワークストリームはシークをサポートせず、コンパレーラが失敗する可能性があります。
Solution: 非シーク可能ストリームをまず 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");
    }
}

パフォーマンスのベストプラクティス

Optimize Memory Usage

  • Buffer Size Tuning: 50 MB 超のドキュメントは内部バッファサイズを 1 MB に増やすと、読み書きサイクルが減少します。
  • Async I/O: ASP.NET Core では非同期ファイル API(FileStream.OpenReadAsync)を使用して I/O 中のスレッドを解放します。

Monitor Resource Consumption

  • Performance Counters: 比較前後で Process.PrivateMemorySize64 を取得し、メモリへの影響を確認します。
  • Benchmarking: dotnet benchmark でファイルベースとストリームベースの比較テストを実行。200 ページ DOCX の場合、ストリームベースは概ね 20‑30 % 高速です。

Concurrency Control

  • Queue System: 同時比較数を CPU コア数に制限し、メモリ不足クラッシュを防止します。
  • Dispose Early: Compare() が返った直後にソース・ターゲットストリームを破棄し、結果ストリームはクライアントへ送信するまで開いたままにします。

実際のユースケース

Use Case 1: Web Application Document Review

SaaS プラットフォームでユーザーが契約書の 2 つのバージョンをアップロードし、IFormFile から MemoryStream に変換して即座に比較、変更点がトラッキングされた DOCX をダウンロード可能にします。

Use Case 2: Batch Processing from Cloud Storage

Azure Function がコンテナ内の新規ブロブをトリガーとして取得し、各ブロブをストリームで読み込み、ベースラインと比較し、結果を別コンテナの “results” フォルダへ書き戻します。

Use Case 3: Version Control Integration

DevOps パイプラインが Git リポジトリから Word ファイルを抽出し、GroupDocs.Comparison にストリームで渡して差分レポートを生成。そのレポートはビルド成果物として監査担当者に添付されます。

Troubleshooting Guide

IssueLikely CauseFix
“Stream does not support reading”書き込み専用ストリーム(例: File.OpenWrite)を渡したFile.OpenRead を使用するか、CanRead が true であることを確認
“Object reference not set to an instance of an object”ストリームが null もしくは比較前に破棄されたストリーム初期化を確認し、Compare() 後まで using ブロックを保持
Poor performance on 100 MB+ filesデフォルトバッファが小さすぎる、または同時タスクが多すぎるバッファサイズを増やし、同時実行数を制限し、dotMemory でプロファイル
Licensing errors in productionライセンスファイルが欠如、またはパスが誤っているアプリケーションルートに GroupDocs.Comparison.lic を配置し、起動時に SetLicense を呼び出す
Corrupted stream dataクラウドストレージからのダウンロード中にネットワークが途切れた比較前にストリーム長とチェックサムを検証

Advanced Configuration Options

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();
    }
}

Definition anchor: CompareOptions は視覚スタイル、パスワード保護、報告対象の変更種別などを制御できる設定オブジェクトです。

一般的な .NET フレームワークとの統合

ASP.NET Core Integration

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 Integration

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 オブジェクトを直接扱い、一時ファイルを回避しながら数千件の同時比較にもスケールできます。上記ベストプラクティス(ストリームの適切な破棄、バッファ調整、ライセンス適用)を守り、堅牢な本番実装を実現してください。

Resources


最終更新日: 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
            }
        }
    }
}

関連チュートリアル