C# Streamとは

# はじめに
C#のStreamクラスがなんのことかさっぱりわからなかったので纏める。
なぜStream継承クラス(FileStream/MemoryStream...)でデータを管理する必要があるのか？変数でいいのでは？と思った。さらにStreamと書いてあると、抽象的でよくわからない…。

# ストリームとは

>プログラミングの分野では、データの入出力全般を扱う抽象的なオブジェクトやデータ型を意味する場合が多い。データが出入りする何らかの対象(メモリ領域やファイル、ネットワークなど)をプログラム中で扱えるように抽象化したもので、接続や切断、書き込みや読み込みなどを簡易な操作で行うことができる。

https://e-words.jp/w/%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%A0.html

ストリームと書いてあることが多いが、データストリームとも呼ぶらしい。ディスク、メモリ、ネットワーク、プロセスの入出力等の何らかの経路を経由して流れてくるデータのことをストリームと呼んでいる。

>データストリーム、もしくは単にストリームとは、一言で言えばひとつながりのデータ列のことを表します。　データにはその実体が存在する場所や、データが流れてくる経路によって様々なものがあります。　例えば、ディスク上のファイルから読み込まれるデータ、メモリ上から読み取られるデータ、ネットワークを経由して転送されてくるデータ、プロセスの標準入出力を経由して渡されるデータなどがありますが、そういった何らかの経路を経て流れてくるデータ列を総称してストリームと呼びます。

ストリームで重要なことはデータがどのような経路から流れてくるのかといった点を抽象化できる点。流れてくるデータ(バイト列)だけに着目できる点。

>ストリームにおいて重要なのは、データがどのような経路から流れてくるのかといった点を抽象化し、特に流れてくるデータだけに着目できるようにするという点です。　経路や実体を抽象化して単なるデータ列と捉えることにより、様々なデータに対する読み込み・書き込みの操作を画一的な方法で扱えるようになります。

https://smdn.jp/programming/netfx/stream/0_abstract/

# Streamクラス
.NETFrameworkにはストリームを扱うためのStreamクラスが用意されている。Streamクラスは単にストリームを表すだけでなく、ストリームに対する読み込み・書き込み・シークといった操作が用意されている。

# FileStreamクラス
Streamクラスの派生型。FileStream.Read()で読み込まれるbyte[]はファイルのエンコーディング方式(文字コードセット)に従ってバイナリデータに変換される。ファイルがUTF-8で保存されていれば、UTF-8でエンコードされる。

# MemoryStreamクラス
MemoryStreamを使って、バイト配列を読み込んだ時。
>MemoryStream も、ある byte[] のサイズ(配列の長さ)とカーソル位置が保持されます。
そして、例えばある byte[] の80バイト目から60バイト分読み出したい、といった場合に、カーソル位置を80バイト目に移動させ、そこから60バイト分読み出すことができます。
これは、次のように書くことができます。
>```c#
>var array = new byte[300];
>
>// --- ここに本来はデータの操作が入る --- //
>
>// array を元に MemoryStream を作成
>var stream = new MemoryStream(array);
>
>// 80バイト目から60バイト読み出す
>var puts = new byte[60];
>stream.Position = 80;
>stream.Read(puts, 0, 60);
>```

使用せずに読み込んだ時。MemoryStream使う意味あるの？
>なお、以下の配列へのアクセスをするコードで、同様の puts を得られます。
>```c#
>var array = new byte[300];
>
>// --- ここに本来はデータの操作が入る --- //
>
>// 80バイト目から60バイト読み出す
>var puts = new byte[60];
>for (int i = 0; i < 60; i++)
>    puts[i] = array[80 + i];
>```
>え、じゃあ何に使うん 。

使う意味あるらしい。
>このプロダクトは文字列や数値としては扱えないデータが山ほどあり(画像や音声、もしかしたら地球外生命体のDNAの解析結果かもしれない)、それらはすべて byte[] で表すことになっています。だから、それら全てのアクセスを、 MemoryStream に置き換えなければなりません。たった1要素の読み込みでさえ、長ったらしく2,3行を書き連ねなければならないのです。
>
>実際にそんなことがあるはずはありません。安心してください。 ところで次の例を見てくれ、こいつをどう思う？

>```c#
>// AESで暗号化するためのオブジェクトを初期化
>var aes = new AesManaged();
>aes.GenerateIV();
>aes.GenerateKey();
>
>// MemoryStreamを作成
>var memStream = new MemoryStream();
>
>// CryptoStreamを作成
>var cryStream = new CryptoStream(memStream, aes.CreateEncryptor, >CryptoStreamMode.Write);
>
>// CryptoStreamに書き込み
>cryStream.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
>
>// MemoryStreamから読み出し
>var read = byte[3];
>memStream.Position = 1;
>memStream.Read(read, 0, 3);
>```
>ちょっと難解ですが、 ある MemoryStream を参照する CryptoStream にデータを書き込むと、 MemoryStream に暗号化されたデータが書き込まれる コードです。
>
>上記の例では read に暗号化されたデータの一部が代入されることになります。
>
>さてこれをちょっとだけ改変しましょう。
>
>```c#
>// AESで暗号化するためのオブジェクトを初期化
>var aes = new AesManaged();
>aes.GenerateIV();
>aes.GenerateKey();
>
>// FileStreamを作成
>var filStream = new FileStream("C:/hoge.enc", FileMode.Create);
>
>// CryptoStreamを作成
>var cryStream = new CryptoStream(filStream , aes.CreateEncryptor, CryptoStreamMode.Write);
>
>// CryptoStreamに書き込み
>cryStream.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
>```
>
>だいたい想像がつくと思いますが、これは ある FileStream を参照する CryptoStream にデータを書き込むと、 FileStream に暗号化されたデータが書き込まれる(暗号化されたデータがファイルに書き込まれる) コードです。
>
>驚くべきことに、このコードを先ほどのコードと比べると、 MemoryStream を FileStream にすり替えただけなのです。


>つまり、 MemoryStream は、 byte[] を FileStream 、すなわち 変数操作とファイル操作と同等に扱えるようにするクラス ということなのです。
C# では、とくにデータの変換系の処理を Stream で行うような風潮があるように見えます。

バイト配列に読み出す。モックや一時的なバッファとして使われることが多い。


https://yuzutan-hnk.hatenablog.com/entry/2017/05/29/020348

# 結論
文字列や数値(変数)では扱えない巨大なデータ(画像や音声等)をbyte配列を持ったストリームで管理するためのクラスと思われる。
(2025/2/17追記)基本的にはStreamにすべて読み出すのではなく、必要な時に必要なデータだけ読みこみ、メモリを節約する。Streamの本質的な強みは、逐次処理(シーケンシャルな読み書き)を可能にすることと、ほかのストリームと組み合わせてデータ変換を行うことにある。



# Stream と IEnumerable<T> / IAsyncEnumerable<T> の類似性
Streamでは必要な時に必要なデータを読み込む使い方が一般的。
IEnumerable<T> / IAsyncEnumerable<T>も同じことが可能。

① 例：List<T> での逐次処理
List<T>はIEnumerable<T>を実装しているため、foreachを使って1 つずつ処理できる。

```cs
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

foreach (var name in names)
{
    Console.WriteLine(name);  // "Alice" → "Bob" → "Charlie" の順に処理
}
```

この場合、namesに含まれる各要素（string 型のオブジェクト）を1つずつ逐次的に処理している。
ただし、List<T>の場合はすべてのデータがメモリ上にあるため、データを遅延取得するわけではないことに注意すること。


② IEnumerable<T> による遅延評価の逐次処理
IEnumerable<T>はデータをすべてメモリに保持しなくても、逐次的に1つずつ処理することができる仕組みを持っている。

例えば、yield returnを使うと、リスト全体をメモリに持たずに、1つずつデータを返すようになる。

```cs
IEnumerable<int> GenerateNumbers()
{
    for (int i = 1; i <= 5; i++)
    {
        yield return i;  // 1 つずつデータを返す
    }
}

foreach (var num in GenerateNumbers())
{
    Console.WriteLine(num);  // 1 → 2 → 3 → 4 → 5 の順に処理
}
```

この場合、リスト全体を作らずに、1つずつデータを生成しながら処理している。