投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/10/20 21:43:25
> 3件のデータが有りますが、1件読んで終わってしまいます。
そのファイルの長さが 528 × 3 = 1584 バイトであることは確認済みでしょうか。

次のレコードの開始位置が何バイト目から開始されるのか、コーディング前に、
実際のファイルの構造をバイナリエディタ等で再確認しておくことをお奨めします。
構造体サイズとファイルサイズとで差が生じていた場合、処理内容によっては
データの読み取り開始位置のズレや不正なメモリアクセスの要因になりえます。


構造体サイズは、sizeof(Form1.userData) では拾えずに、
 Marshal.SizeOf<Form1.usrData>() … 先の私の回答
 Marshal.SizeOf(typeof(Form1.usrData)) … 先の KOZ さんの回答
などで取得できるわけですが、そこから得られたであろう 528 という値が
本来処理したいファイルに対して、適切なサイズであるかどうか確認しておきましょう。

今回のデータ構造はアライメントを意識したレイアウト設計にはなっていないようなので、
StructLayout で Pack (と Charset) を明示しておくことをお奨めします。

最初のコードだと、userLevel フィールドの後に、4 バイトのパディングが含まれますが、
たとえば、StructLayout で CharSet.Ansi, Pack = 1 を指定した場合は
そのパディングが削られ、528 バイトでは無く 524 バイトというサイズになります。


> readSize = fs.Read(buf, bufPos, Math.Min(528, remain));
Read メソッドに Math.Min を使っておられますが、
 Read(byte[] buffer, int offset, int count);
の count を変動させる必要は無く、固定値的に
「読み取りたいデータサイズ」つまり構造体サイズを指定するだけで十分です。

Read メソッドの戻り値が count と同じであれば、データを正しく読み取れたことになりますので、
バイナリを構造体へ転写した後、再度、次のブロックを Read するためにループ処理します。

一方、Read の戻り値が 0 になった場合は、既にファイル終端に到達していることを
意味しますので、そこでループを脱出するようにします。

そして Read の戻り値が 1 以上かつ count 未満であった場合には、
ファイルの残りの長さが構造体サイズに満たなかった事を意味します。
この場合、バッファーには読み取れた部分までが転写された状態になります。

Read されたデータサイズが構造体サイズに満たなかった場合に対する振る舞いとしては、
 「読み取れた部分まで処理できれば良いので、不足時は戻り値が 0 の時と同様に break; する」
 「不足時はファイル フォーマットの破損を意味するので、エラーメッセージを出して終了する」
 「足りないデータは初期値で置き換えるよう、データ補間して構造体に渡すように設計する」
などの選択肢が思い当たりますが、どう扱うかはプログラムの設計次第ですね。


> fs.Dispose();
> fs.Close();
FileStream に対して、両方を呼び出す必要はありません。どちらか一方で十分です。
迷った場合は、「using ブロックで囲む」もしくは「Close を呼び出す」のが良いでしょう。


仮に両方呼び出すにしても、Close を先に呼び、Dispose を後にすることをお奨めします。
Dispose 後に、そのオブジェクトの public なメソッドやプロパティを呼ぶことは、
さほど一般的ではありません。

というのも、オブジェクトによっては
「Dispose で処分した後に何か処理をさせようとすると、ObjectDisposedException 例外になる」
という振る舞いをするものがあるためです。

まぁ FileStream の場合は、Dispose 後に Close を呼んでも構わないのですが、
もしもあえて両方書くなら、Close → Dispose の方が望ましいでしょう。
(クラスによっては、Dispose せずに Close したオブジェクトを、後から再 Open 出来る設計がありえます)


なお、FileStream の内部実装的な話をすると:

Dispose() メソッドは、「Close() を呼び出す」だけの処理です。
https://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,251

Close() メソッドは、「Dispose(true)の呼び出し」後にファイナライズを抑制する処理です。
https://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,232

そして Dispose(bool) メソッドが 『後始末』をする処分処理の本体ですが、
これは public ではなく protected であり、外部からではなく内部的に呼ばれます。具体的には
 「ファイナライザから Dispose(false)される」
 「Close() から Dispose(true) される」
 「Dispose() から Close() されることで、Dispose(true) が呼ばれる」
といったルートで呼び出される設計です。
https://referencesource.microsoft.com/#mscorlib/system/io/filestream.cs,1270