ストリーム

クラス階層

  • System.Object
    • System.IO.File
    • System.IO.Directory
    • System.IO.Path
    • System.IO.BinaryReader … バイナリの読み取り (バイナリと基本型を変換)
    • System.IO.BinaryWriter
    • System.MarshalByRefObject
      • System.IO.Stream
        • System.IO.FileStream … バイト配列の読み書き
        • System.IO.MemoryStream
        • System.IO.Pipes.PipeStream
        • System.IO.UnmanagedMemoryStream
      • System.IO.FileSystemInfo
      • System.IO.TextReader … 文字列の読み取り
        • System.IO.StreamReader … 文字列の読み取り (バイトと文字を変換)
        • System.IO.StringReader … (文字と文字列を変換)
      • System.IO.TextWriter … 文字列の書き込み
      • System.ComponentModel.Component
    • System.Xml.XmlWriter
      • System.Xml.XmlTextWriter
    • Microsoft.VisualBasic.FileIO.FileSystem
ストリーム - ファイルおよびストリーム入出力 - .NET | Microsoft Learn

ストリームの破棄

Streamをコンストラクタの引数に取るクラスをusingでネストすると、コード分析CA2202で警告されます。

using (Stream stream = new FileStream("sample.txt", FileMode.Create))
using (StreamWriter writer = new StreamWriter(stream))
{
    // writerオブジェクトを使用する処理
} // warning CA2202: Microsoft.Usage : オブジェクト 'stream' は、メソッド '***' 内で 2 回以上破棄される可能性があります。System.ObjectDisposedException の生成を回避するには、オブジェクトに対して Dispose を 2 回以上呼び出さないようにしてください。

よってtry-finallyで破棄するようにします。

Stream stream = null;
try
{
    stream = new FileStream("sample.txt", FileMode.Create);
    using (StreamWriter writer = new StreamWriter(stream))
    {
        stream = null; // StreamWriterの生成に成功したならばそのDispose()で破棄されるため、Stream.Dispose()が呼ばれないようにnullを設定

        // writerオブジェクトを使用する処理
    }
}
finally
{
    if (stream != null) stream.Dispose();
}

ファイル

FileStream

次のようにすると、”ABC”という文字列を含む"sample.txt"というファイルが作成されます。

string path = "sample.txt";
using (FileStream fs = new FileStream(path, FileMode.Create))
{
    byte[] array = new byte[] { 65, 66, 67 }; // ABC
    fs.Write(array, 0, array.Length);
} // ここでストリームが閉じられる

ストリームは閉じられるときに書き込まれますので、usingなどで明示的にリソースを解放します。

コンストラクタ

public FileStream (
    string path,
    System.IO.FileMode mode,
    System.Security.AccessControl.FileSystemRights rights,
    System.IO.FileShare share,
    int bufferSize,
    System.IO.FileOptions options,
    System.Security.AccessControl.FileSecurity fileSecurity
    );
FileStream(String, FileMode, FileSystemRights, FileShare, Int32, FileOptions, FileSecurity) - FileStream コンストラクター (System.IO) | Microsoft Learn

FileStreamのインスタンスは、次のようにFile.Create()メソッドでも作成できます。

FileStream fs = File.Create(path)
File.Create メソッド (String) (System.IO) | MSDN

modeを指定したときに「パス '***' へのアクセスが拒否されました。」としてUnauthorizedAccessExceptionが投げられるならば、それに適合するようにshareも指定します。

メソッド

メソッド  
CopyTo(Stream) 現在のストリームから、別のストリームへ書き込む
Read(Byte[], Int32, Int32) 指定位置から指定バイト数だけ読み取り、指定のバッファへ書き込む
ReadByte() 現在位置から1バイト読み取り、それを返す
Write(Byte[], Int32, Int32)  
WriteByte(Byte)  
非同期のメソッド (すべてStreamからの継承)
メソッド  
CopyToAsync(Stream)  
ReadAsync(Byte[], Int32, Int32)  
WriteAsync(Byte[], Int32, Int32)  
BeginRead(Byte[], Int32, Int32, AsyncCallback, Object) 非推奨。代わりにReadAsyncを使用する
BeginWrite(Byte[], Int32, Int32, AsyncCallback, Object) 非推奨。代わりにWriteAsyncを使用する
メソッド - FileStream クラス (System.IO) | MSDN
読み込み
同期
public override int Read(
    byte[] array,
    int offset,
    int count
)
FileStream.Read メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
非同期
[ComVisibleAttribute(false)]
[HostProtectionAttribute(SecurityAction.LinkDemand, ExternalThreading = true)]
public Task<int> ReadAsync(
    byte[] buffer,
    int offset,
    int count
)
Stream.ReadAsync メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
書き込み
同期
public override void Write(
    byte[] array,
    int offset,
    int count
)
FileStream.Write メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
非同期
[ComVisibleAttribute(false)]
[HostProtectionAttribute(SecurityAction.LinkDemand, ExternalThreading = true)]
public Task WriteAsync(
    byte[] buffer,
    int offset,
    int count
)
Stream.WriteAsync メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
public async void Method()
{
    // await式に到達するまでは、同期的に実行される

    using (FileStream fs = new FileStream("sample.txt", FileMode.Create))
    {
        byte[] array = new byte[1000 * 1000];
        await fs.WriteAsync(array, 0, array.Length); // ここで呼び出し元へ制御が戻る

        // 書き込みが完了すると、これ以降が処理される
    }
}

awaitで待たせないと、書き込み完了前にストリームが閉じられることがあります。ファイル アクセスにおける非同期の使用 (C#) | Microsoft Learn

バイト以外の配列

配列がbyte型ならば、Write(Byte[])などのバイト配列向けのメソッドだけで処理できます。しかしそれ以外の型ならば、要素を1つずつ処理します。

int[] items = new int[] { 1, 2, 3 };

// 書き込み
FileStream fileStream = null;
try
{
    fileStream = File.Create("sample.dat");
    using (BinaryWriter binaryWriter = new BinaryWriter(fileStream))
    {
        fileStream = null;
        foreach (int item in items)
        {
            binaryWriter.Write(item);
        }
    }
}
finally
{
    if (fileStream != null) fileStream.Dispose();
}

// 読み取り
int[] result;
fileStream = null;
try
{
    fileStream = File.OpenRead("sample.dat");
    using (BinaryReader binaryReader = new BinaryReader(fileStream))
    {
        fileStream = null;
        long length = binaryReader.BaseStream.Length / sizeof(int);
        result = new int[length];

        for (int i = 0; i < length; i++)
        {
            result[i] = binaryReader.ReadInt32();
        }
    }
}
finally
{
    if (fileStream != null) fileStream.Dispose();
}

メモリ

MemoryStream

public MemoryStream ();
MemoryStream() - MemoryStream Constructor (System.IO) | Microsoft Learn
byte[] src = new byte[] { 1, 2, 3 };
byte[] dest;

using (MemoryStream memoryStream = new MemoryStream())
{
    // 書き込む
    memoryStream.Write(src, 0, src.Length);

    // ストリームの位置を先頭へ戻す
    memoryStream.Seek(0, SeekOrigin.Begin);

    // 読み込む
    dest = new byte[memoryStream.Length];
    int count = memoryStream.Read(dest, 0, dest.Length);
}

WriteTo()

ストリームの全体の内容を、他のストリームへ書き込めます。

public virtual void WriteTo (System.IO.Stream stream);
MemoryStream.WriteTo(Stream) Method (System.IO) | Microsoft Learn

内部ではStream.Write()で書き込まれます。WriteTo - memorystream.cs

UnmanagedMemoryStream

[System.CLSCompliant(false)]
[System.Security.SecurityCritical]
public UnmanagedMemoryStream (
    byte* pointer,              // アンマネージド メモリへのポインタ
    long length,                // 使用するメモリの長さ
    long capacity,              // ストリームに割り当てられたメモリの総量
    System.IO.FileAccess access // FileAccessの値
    );
UnmanagedMemoryStream(Byte*, Int64, Int64, FileAccess) - UnmanagedMemoryStream Constructor (System.IO) | Microsoft Learn
byte[] array = { 1, 2, 3 };

int size = Marshal.SizeOf(array[0]) * array.Length;
IntPtr intPtr = Marshal.AllocHGlobal(size);

unsafe
{
    // IntPtrからbyteポインタを取得する
    byte* bytePtr = (byte*)intPtr.ToPointer();

    // アンマネージド メモリへのポインタを使用するUnmanagedMemoryStreamオブジェクトを作成する
    using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream(bytePtr, array.Length, array.Length, FileAccess.Write))
    {
        // データを書き込む
        stream.Write(array, 0, array.Length);

        // ストリームを閉じる
        stream.Close();
    }
}

Marshal.FreeHGlobal(intPtr);

バイナリ

BinaryWriter

プリミティブ型のバイナリでの書き込みと、指定のエンコーディングでの文字列の書き込みに対応しています。

// 書き込み
FileStream fileStream = null;
try
{
    fileStream = File.Open("sample.dat", FileMode.Create);
    using (BinaryWriter binaryWriter = new BinaryWriter(fileStream))
    {
        fileStream = null;

        binaryWriter.Write(10);   // 0x0a 0x00 0x00 0x00
        binaryWriter.Write(1.0f); // 0x00 0x00 0x80 0x3F
        binaryWriter.Write(true); // 0x01
        binaryWriter.Write("ab"); // 0x02 0x61 0x62
    }
}
finally
{
    if (fileStream != null) fileStream.Dispose();
}
BinaryWriter クラス (System.IO) | MSDN

BinaryReader

// 読み取り
FileStream fileStream = null;
try
{
    fileStream = File.Open("sample.dat", FileMode.Open);
    using (BinaryReader binaryReader = new BinaryReader(fileStream))
    {
        fileStream = null;

        int a1 = binaryReader.ReadInt32();   // 10
        float a2 = binaryReader.ReadSingle();  // 1
        bool a3 = binaryReader.ReadBoolean(); // true
        string a4 = binaryReader.ReadString();  // "ab"

        // すべてのデータの読み取り
        byte[] result = new byte[binaryReader.BaseStream.Length];
        binaryReader.Read(result, 0, result.Length);
    }
}
finally
{
    if (fileStream != null) fileStream.Dispose();
}
BinaryReader クラス (System.IO) | MSDN

ReadString()で読み取った文字列の先頭には、文字列の長さが付加されます。これが期待するものでなければReadBytes()でbyte配列として読み取り、Encoding.GetString()で文字列にデコードします。

ファイルの末尾までの順次読み取り

BaseStreamプロパティからStreamを取得し、そのPositionがLengthより小さいことで末尾に達していないことを確認できます。binaryfiles - C# checking for binary reader end of file - Stack Overflow

Stream stream = binaryReader.BaseStream;
while(stream.Position < stream.Length)
{
    // 読み取り処理
}

テキスト

StreamWriter

コンストラクタ

public StreamWriter(
    string path
)
StreamWriter コンストラクター (String) (System.IO) | MSDN

ファイルが存在する場合には上書きされます。これを追記するようにするには、次の形式でappendをtrueとします。

public StreamWriter(
    string path,
    bool append // 追記するかどうか
)
StreamWriter コンストラクター (String, Boolean) (System.IO) | MSDN

既定では文字エンコーディングはUTF-8となります。これを変更するには、encodingの指定を追加します。

public StreamWriter(
    string path,
    bool append,      // 追記するかどうか
    Encoding encoding // 文字エンコーディング
)
StreamWriter コンストラクター (String, Boolean, Encoding) (System.IO) | MSDN
string path = "sample.txt";
using (StreamWriter sw = new StreamWriter(path))
{
    sw.Write("ABC");
}

メソッド

メソッド 機能
Write(String)  
Write(String, Object) 書式設定してストリームへ書き込める
WriteAsync(String) 非同期で書き込める
WriteLine(String) 行の終端記号も書き込める
   
メソッド - StreamWriter クラス (System.IO) | MSDN
public override void Write(
    string value
)
StreamWriter.Write メソッド (String) (System.IO) | MSDN

StreamReader

  • System.Object
    • System.MarshalByRefObject
      • System.IO.TextReader
        • System.IO.StreamReader
StreamReader クラス (System.IO) | MSDN

コンストラクタ

public StreamReader(
    string path
)
StreamReader コンストラクター (String) (System.IO) | MSDN

プロパティ

プロパティ  
Encoding CurrentEncoding 現在の文字エンコーディング
bool EndOfStream ストリームの末尾に達しているならば、true
プロパティ - StreamReader クラス (System.IO) | MSDN

メソッド

戻り値の型 メソッド 読み込み基準 読み込み範囲 末尾から読み込んだときの戻り値
int Read() ストリームの現在位置 1文字 -1
int Read(Char[], Int32, Int32) ストリームの指定位置 指定文字数 0
int ReadBlock(Char[], Int32, Int32) ストリームの指定位置 指定文字数 0
string ReadLine() ストリームの現在位置 1行 null
string ReadToEnd() ストリームの現在位置 末尾まで ""
メソッド - StreamReader クラス (System.IO) | Microsoft Learn
非同期のメソッド
戻り値の型 メソッド
Task<int> ReadAsync(Char[], Int32, Int32)
Task<int> ReadBlockAsync(Char[], Int32, Int32)
Task<string> ReadLineAsync()
Task<string> ReadToEndAsync()
ReadLine()

\n、\r、\rnまでが1行と見なされます。 Remarks - StreamReader.ReadLine Method (System.IO) | Microsoft Learn ReadLine - streamreader.cs

ReadToEnd()
public override string ReadToEnd ();
StreamReader.ReadToEnd メソッド (System.IO) | Microsoft Learn
using (StreamReader sr = new StreamReader("samle.txt"))
{
    string text = sr.ReadToEnd();
}
c# - Difference between StreamReader.Read and StreamReader.ReadBlock - Stack Overflow

ストリームが末尾に達していないときにこのメソッドを呼び出すと、無期限にブロックされます。

StringWriter

このクラスはIDisposableを実装しますが、それで処置するリソースはないため、Dispose()を呼ぶ必要はありません。Remarks - StringWriter Class (System.IO) | Microsoft Learn

文字コードは既定でSystem.Text.UnicodeEncodingとなるため、これを変更するにはこのクラスを継承し、Encodingプロパティから希望する文字コードを返します。xml - XmlWriter encoding UTF-8 using StringWriter in C# - Stack Overflow

StringReader

StringReaderは、文字列からしか初期化できません。

public StringReader(
    string s
)
StringReader(String) コンストラクター (System.IO) | Microsoft Learn

このクラスはIDisposableを実装しますが、それで処置するリソースはないため、Dispose()を呼ぶ必要はありません。Remarks - StringReader Class (System.IO) | Microsoft Learn

CSV

書き込み

書き込み用のクラスなどは用意されていないため、自前で処理します。c# - Best practices for serializing objects to a custom string format for use in an output file - Stack Overflow

定義に従うならばエスケープの処理などが必要ですが、簡易で良ければString.Join()でカンマで連結し、StreamWriterのWriteLine()でファイルに書き込めます。

string[][] data = new string[][]
{
    new string[]{ "a", "b", "c" },
    new string[]{ "d", "e" }
};

using (StreamWriter streamWriter = new StreamWriter("sample.csv"))
{
    foreach (string[] item in data)
    {
        streamWriter.WriteLine(String.Join(",", item));
    }
}

読み込み (TextFieldParser)

TextFieldParserクラスで読み込めます。

Microsoft.VisualBasic.FileIO.TextFieldParser parser
    = new Microsoft.VisualBasic.FileIO.TextFieldParser("sample.csv");

// 区切り文字を設定する。これは既定ではnullとなっており、設定しなければ読み込み時に「区切り記号が Nothing または empty であるため、区切り記号で分けられたフィールドを読み取れません。」としてArgumentExceptionが投げられる
parser.SetDelimiters(",");

List<string[]> result = new List<string[]>();
while (!parser.EndOfData)
{
    // 現在の行から、指定形式でフィールドを読み込む
    string[] fields = parser.ReadFields();
    result.Add(fields);
}

// 結果を配列として得たいならば、リストを配列に変換する
string[][] array = result.ToArray();

TextFieldParserの利用時に「型または名前空間の名前 'FileIO' が名前空間 'Microsoft.VisualBasic' に存在しません」としてエラーとなるときには、Microsoft.VisualBasicを参照に追加します。

プロパティ
プロパティ 内容 既定値
string[] CommentTokens コメント行と見なす文字列 string[0]
FieldType TextFieldType テキストファイルの形式 FieldType.Delimited
string[] Delimiters 区切り記号。TextFieldTypeがDelimitedのときに必須 null
int[] FieldWidths テキストファイルの各列の幅。TextFieldTypeがFixedWidthのときに必須 null
bool TrimWhiteSpace trueならば、前後の空白を除去する true
       
プロパティ - TextFieldParser クラス (Microsoft.VisualBasic.FileIO) | Microsoft Learn

Delimitersにカンマ以外を設定すれば、CSV以外の形式にも対応できます。またTextFieldTypeをFixedWidthとしてFieldWidthsでその幅を指定すると、固定幅でも読み込めます。

FieldType 列挙型
列挙子 フィールドの形式
Delimited 区切り文字で分割
FixedWidth 固定幅で分割
FieldType 列挙型 (Microsoft.VisualBasic.FileIO) | MSDN

ReadFields()

現在の行のすべてのフィールドを、文字列の配列として得られます。そして次にデータが含まれる行までカーソルが進みます。

public string[] ReadFields()
TextFieldParser.ReadFields メソッド (Microsoft.VisualBasic.FileIO) | MSDN
StringReader stringReader = new StringReader("AA,\"BB,BB\",\"C\"\"C\"\r\nDD,\"EE\r\nEE\"");

TextFieldParser parser = new TextFieldParser(stringReader);
parser.SetDelimiters(",");

string[] fields1 = parser.ReadFields(); // AA, BB,BB, C\"C
string[] fields2 = parser.ReadFields(); // DD, EE\r\nEE
  • 空行はすべて飛ばされ、空行ではない最初の行の解析結果が返される。そしてカーソルは、空行ではない行まで進む。
  • 解析に失敗した場合は例外が発生し、カーソルは進まない。

ReadFields()では空行が読み飛ばされるため、それが期待する動作ではないならば、行を追加するなどの処理が必要です。.net - Why does TextFieldParser.ReadField remove consecutive newlines from middle of a field? - Stack Overflow

これと類似するメソッドとは、下表のような違いがあります。

戻り値 メソッド 機能 構文解析 カーソル 空行
string[] ReadFields() 現在の行を文字配列として読み込む あり 次の行へ進む 無視する
string ReadLine() 現在の行を文字として読み込む なし 無視しない
string PeekChars(Int32) 指定数の文字を読み込む 進まない 無視する

XML

複雑な操作が必要ならば、XmlDocumentを用います。c# - XmlDocument vs XmlWriter - Stack Overflow

XmlWriter

.NET 2.0以降はXmlTextWriterの代わりに、このXmlWriterクラスを用います。Remarks - XmlTextWriter Class (System.Xml) | Microsoft Learn

public static System.Xml.XmlWriter Create (
    string outputFileName,
    System.Xml.XmlWriterSettings settings
    );
Create(String, XmlWriterSettings) - XmlWriter.Create Method (System.Xml) | Microsoft Learn

XMLの作成方法はsettingsで指定します。XmlWriterSettings Class (System.Xml) | Microsoft Learn

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;

using (XmlWriter writer = XmlWriter.Create("sample.xml", settings))
{
    writer.WriteComment("SAMPLE");
    writer.WriteStartElement("root");

    writer.WriteStartElement("e1");
    writer.WriteAttributeString("a1", "10");

    writer.WriteStartElement("e2");
    writer.WriteAttributeString("a2", "20");
    writer.WriteString("ABC");
    writer.WriteEndElement();

    writer.WriteStartElement("e2");
    writer.WriteAttributeString("a3", "30");
    writer.WriteString("DEF");
    writer.WriteEndElement();

    writer.WriteEndElement();
    writer.WriteEndElement();
}

このコードは次のようなファイルを出力します。

<?xml version="1.0" encoding="utf-8"?>
<!--SAMPLE-->
<root>
  <e1 a1="10">
    <e2 a2="20">ABC</e2>
    <e2 a3="30">DEF</e2>
  </e1>
</root>

最後にClose()を呼ぶ必要がありますが、これはDispose()でも呼ばれます。

WriteStartDocument()

public abstract void WriteStartDocument (bool standalone);
WriteStartDocument(Boolean) - XmlWriter.WriteStartDocument Method (System.Xml) | Microsoft Learn

standaloneにtrueを渡すことで、XML宣言に"standalone=yes"を追記できます。XML宣言をより柔軟に出力するには、WriteProcessingInstruction()を用います。

WriteProcessingInstruction()

public abstract void WriteProcessingInstruction (
    string name,
    string text
    );
XmlWriter.WriteProcessingInstruction(String, String) Method (System.Xml) | Microsoft Learn

XmlReader

.NET 2.0以降はXmlTextReaderの代わりに、このXmlReaderクラスを用います。Remarks - XmlTextReader Class (System.Xml) | Microsoft Learn

public static System.Xml.XmlReader Create (
    System.IO.Stream input,               // XMLデータを含むストリーム
    System.Xml.XmlReaderSettings settings // 新しいXmlReaderインスタンスの設定
    );
Create(Stream, XmlReaderSettings) - XmlReader.Create メソッド (System.Xml) | Microsoft Learn

settingsを省略したときはDefault settingsの設定値が用いられます。

using (XmlReader reader = XmlReader.Create("sample.xml"))
{
    reader.MoveToContent();
    Console.Write(reader.Name); // "root"

    reader.ReadToFollowing("e2");
    Console.Write(reader.GetAttribute("a2")); // "20"
    Console.Write(reader.ReadElementContentAsString()); // "ABC"

    XmlNodeType type1 = reader.NodeType; // Whitespace
    reader.Skip();
    XmlNodeType type2 = reader.NodeType; // Element

    Console.Write(reader.GetAttribute("a3")); // "30"
    Console.Write(reader.ReadElementContentAsString()); // "DEF"
}

ReadSubtree()

現在のノードとその子孫ノードを読み込める新しいXmlReaderを得られます。これによりアクセスできる範囲を制限できます。

public virtual System.Xml.XmlReader ReadSubtree ();
XmlReader.ReadSubtree メソッド (System.Xml) | Microsoft Learn

サブツリー全体が読み込まれると、 Read()はfalseを返します。このメソッドから得られるXmlReaderが閉じられると、元のXmlReaderはサブツリーのノードのEndElementの位置になります。

新しいXmlReaderを閉じるまで、元のXmlReaderを操作しないようにします。そのような操作には対応しておらず、予期せぬ結果となります。Remarks - XmlReader.ReadSubtree Method (System.Xml) | Microsoft Learn

圧縮

GZipStream (gzip)

圧縮したファイルをツールで展開できるようにするには、DeflateStreamではなくGZipStreamを用います。c# - GZipStream or DeflateStream class? - Stack Overflow

FileInfo file = new FileInfo("sample.txt");
FileInfo newFile = new FileInfo(file.FullName + ".gz");

using (FileStream original = file.OpenRead())    // 圧縮前のファイル
using (FileStream compressed = newFile.Create()) // 圧縮後のファイル
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Compress))
{
    original.CopyTo(gzip);
}

これを展開するには、CompressionModeをDecompressとします。

FileInfo file = new FileInfo("sample.txt.gz");
FileInfo newFile = new FileInfo("sample.txt");

using (FileStream compressed = file.OpenRead())    // 展開前のファイル
using (FileStream decompressed = newFile.Create()) // 展開後のファイル
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Decompress))
{
    gzip.CopyTo(decompressed);
}

ZipFile (ZIP)

ZIPファイルへストリームから書き込むには、次のようにします。c# - Creating a ZIP Archive in Memory Using System.IO.Compression - Stack Overflow

using (FileStream fileStream = new FileStream("sample.zip", FileMode.Create))
using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Create))
{
    // File 1
    ZipArchiveEntry archiveEntry1 = archive.CreateEntry("file1.txt");
    using (StreamWriter stream = new StreamWriter(archiveEntry1.Open()))
    {
        stream.Write("sample1");
    }

    // File 2
    ZipArchiveEntry archiveEntry2 = archive.CreateEntry("file2.txt");
    using (StreamWriter stream = new StreamWriter(archiveEntry2.Open()))
    {
        stream.Write("sample2");
    }
}

逆にストリームへ読み込むには、次のようにします。

using (FileStream fileStream = new FileStream("sample.zip", FileMode.Open))
using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
    // File 1
    ZipArchiveEntry archiveEntry1 = archive.GetEntry("file1.txt");
    using (StreamReader stream = new StreamReader(archiveEntry1.Open()))
    {
        string str = stream.ReadLine(); // "sample1"
    }

    // File 2
    ZipArchiveEntry archiveEntry2 = archive.GetEntry("file2.txt");
    using (StreamReader stream = new StreamReader(archiveEntry2.Open()))
    {
        string str = stream.ReadLine(); // "sample2"
    }
}

参考

参考書

Microsoft Learnから検索