第44章:Decorator ②:.NET最強例(Streamデコレータ)💧
ねらい 🎯✨
- **Decorator(デコレータ)=「同じインターフェースのまま、機能を重ね着する」**を、.NETのど真ん中で体感するよ🧥➕🧥
- 特に
Streamまわりは “デコレータの宝庫” なので、ここが分かると一気に設計が気持ちよくなる☺️🌈
到達目標 ✅🌟
Stream系APIを見たときに「これDecoratorっぽい!」を見抜ける👀✨BufferedStream/GZipStream/CryptoStreamを「重ね着」して使える🧩💫- Dispose / close / leaveOpen / FlushFinalBlock の “事故りポイント” を回避できる🧯⚠️
手順 🧭💡
1) まず “土台” を確認:Stream は契約(インターフェース)そのもの 📜

Stream は すべてのストリームの抽象基底クラスで、読み書きの共通メソッドを提供するよ📦✨
つまり「ここ(Stream)を満たしていれば、差し替えOK」っていう強い契約! (Microsoft Learn)
Decoratorで重要なのはここ👇
- “包む側” も “包まれる側” も 同じ型(
Stream)として扱える - だから チェーン(重ね着) ができる 🔗✨
2) 見抜き方:Decoratorっぽい .NET API の特徴 🔍✨
次のどれかが見えたら、だいたいDecorator候補だよ🕵️♀️
- コンストラクタで
Streamを受け取る(=中身を抱えて包む) - 自分も
Streamを継承してる(=同じ顔でふるまう) - “付け足し機能” を持つ(バッファ / 圧縮 / 暗号化 など)
3) BufferedStream:速度(バッファ)を後付けする 🚀🧃
BufferedStream は 別の Stream に対して、読み書きのバッファ層を追加するクラスだよ🧤✨(まさにデコレータ) (Microsoft Learn)
しかも、bufferSize を省略すると 4096バイトが既定になる(この数字、地味に重要)🧠📌 (Microsoft Learn)
使いどころイメージ
- 小さい書き込みを何回もする → バッファ無しだと遅くなりやすい😵💫
- そこで
BufferedStreamを “1枚羽織る” → まとめて書いてくれる✨
ミニ例(ファイルにバイト列を書くだけ)📝
using System.Text;
var path = "sample.bin";
var data = Encoding.UTF8.GetBytes("Hello BufferedStream! 🧡");
await using var file = File.Create(path);
await using var buffered = new BufferedStream(file); // 既定 4096 bytes
await buffered.WriteAsync(data);
await buffered.FlushAsync();
4) GZipStream:圧縮を後付けする 🗜️🎀
GZipStream も Stream を受け取って包めるから、チェーンに参加できるよ🔗✨
そして重要なのが leaveOpen:必要に応じて、下のストリームを開いたままにできるやつ! (Microsoft Learn)
さらに、GZipStream は “包んだ下のストリーム” を 所有して Dispose する(=勝手に閉じる)側の動きが基本になる点も押さえてね🧯 (Microsoft Learn)
圧縮して保存→復元の最小サンプル(MemoryStreamで安全に)🧪
using System.IO.Compression;
using System.Text;
static byte[] CompressUtf8(string text)
{
var input = Encoding.UTF8.GetBytes(text);
using var output = new MemoryStream();
using (var gzip = new GZipStream(output, CompressionLevel.Optimal, leaveOpen: true))
{
gzip.Write(input, 0, input.Length);
} // gzipをDispose → 圧縮の終端を書いてくれる
return output.ToArray();
}
static string DecompressUtf8(byte[] gzData)
{
using var input = new MemoryStream(gzData);
using var gzip = new GZipStream(input, CompressionMode.Decompress);
using var plain = new MemoryStream();
gzip.CopyTo(plain);
return Encoding.UTF8.GetString(plain.ToArray());
}
var gz = CompressUtf8("Hello GZipStream! 🗜️✨");
var text = DecompressUtf8(gz);
Console.WriteLine(text);
5) CryptoStream:暗号化を後付けする 🔐✨
CryptoStream は 暗号変換(Transform)をストリームにかぶせる超ど真ん中のDecoratorだよ🧙♀️🔗
しかも公式にも、複数の CryptoStream をチェーンできるって書いてある(重ね着OK!) (Microsoft Learn)
ここで最重要⚠️
CryptoStream は 最後のブロック(終端)を書かないと復号できない事故が起きやすいの…😇
だから「終わり」を確実にする必要があるよ👇
FlushFinalBlock()を呼ぶと 下のデータソースを更新して内部バッファをクリアしてくれる (Microsoft Learn)- さらに
Close()はFlushFinalBlock()を呼び、下のストリームも Close する(つまり using/Dispose が超大事) (Microsoft Learn)
6) “重ね着”こそ本番:圧縮→暗号化(書く)/ 復号→解凍(読む)🔗🧥✨
Decoratorは 順番で意味が変わるよ!ここが楽しくて怖いところ😆⚠️
おすすめの考え方(超ざっくり)
- 書くとき:圧縮 → 暗号化(圧縮したいのは “平文” 側だから)🗜️➡️🔐
- 読むとき:復号 → 解凍(書いた順の逆)🔐➡️🗜️
ファイルに「圧縮してから暗号化して保存」する例🧩
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
static void WriteCompressedEncrypted(string path, string text, byte[] key, byte[] iv)
{
var plain = Encoding.UTF8.GetBytes(text);
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var file = File.Create(path);
// file <- crypto(encrypt) <- gzip(compress) <- Write(plain)
using var crypto = new CryptoStream(file, aes.CreateEncryptor(), CryptoStreamMode.Write);
using var gzip = new GZipStream(crypto, CompressionLevel.Optimal);
gzip.Write(plain, 0, plain.Length);
// gzip.Disposeで圧縮の終端が出る → crypto.DisposeでFlushFinalBlock相当も確実に走るイメージ✨
}
static string ReadDecryptedDecompressed(string path, byte[] key, byte[] iv)
{
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var file = File.OpenRead(path);
// file -> crypto(decrypt) -> gzip(decompress) -> plain
using var crypto = new CryptoStream(file, aes.CreateDecryptor(), CryptoStreamMode.Read);
using var gzip = new GZipStream(crypto, CompressionMode.Decompress);
using var plain = new MemoryStream();
gzip.CopyTo(plain);
return Encoding.UTF8.GetString(plain.ToArray());
}
ポイントまとめ🧠✨
usingで外側から順に Dispose される → 終端処理が自動で走りやすい🙆♀️leaveOpenは「このデコレータを外したあとも下を使う」必要があるときだけ使う(多用しない) (Microsoft Learn)CryptoStreamの “終端” は超重要(Dispose/Close で確実に)(Microsoft Learn)
7) Visual Studioで “Decoratorの証拠” を取るコツ 🕵️♀️🧾
APIを見たら、次をチェックして「Decoratorだ!」って確信しよう💪✨
- 型が
Stream派生か(class X : Stream的な) (Microsoft Learn) - コンストラクタに
Streamがあるか(包んでるか) leaveOpenみたいな “下を閉じる/閉じない” の選択肢があるか (Microsoft Learn)- ドキュメントに「下のストリームをDisposeする」系が書いてあるか (Microsoft Learn)
よくある落とし穴 🕳️⚠️
-
順番ミスで意味が変わる 🔄😵 圧縮と暗号化は “順番が仕様” になりがち。読む側は必ず逆順!
-
leaveOpenの誤用 🧷💥 便利だからって全部leaveOpen: trueにすると、今度は「誰が閉じるの?」問題が発生しがち🫠 → “最後に閉じる担当” を1つに決めるのがコツ! -
CryptoStreamの終端事故 🔐💣
FlushFinalBlock()/Close()/Dispose()のどれかを確実に通さないと、復号で壊れることがあるよ😇 →usingを信じて、チェーンを綺麗に組む! (Microsoft Learn) -
全部盛りにしてデバッグ不能 🍔💦 一気に
Buffered + GZip + Crypto + ...にすると、どこで壊れたか分からなくなる😭 → 1枚ずつ足して、テストで固定しよう🧪✨
ミニ演習(10〜30分)🧪🎀
次を この順で やってね(1つずつ成功させるのがコツ)🌱
MemoryStream+GZipStreamで「文字列の圧縮→復元」✅🗜️- 1が通ったら、次に
CryptoStreamを足して「圧縮→暗号化→復号→解凍」✅🔐 - 最後に
BufferedStreamを足して、処理時間(or 体感)を比べる✅🚀
テスト観点(最低これだけ)
- 復元文字列が元と一致する
- 空文字でも動く
- ちょい長文(1万文字くらい)でも動く
自己チェック ✅🔍
Streamを受け取るコンストラクタを見たら「Decoratorかも」と思える?👀leaveOpenを “必要なときだけ” 使う判断ができる?🧷CryptoStreamはusingで終端処理まで確実に通す意識がある?🔐✨ (Microsoft Learn)- 圧縮と暗号化の順番を説明できる?🗜️➡️🔐 / 🔐➡️🗜️