VB.NET|Image.FromStreamとUsingの正しい関係:InputStreamを閉じない安全設計と実装パターン【.NET Framework 4.0/4.8・WinForms】

VB.NET で画像を扱うときに必ずと言っていいほど出会うのが Image.FromStream と Using の取り合わせです。「Using を抜けたらストリームは勝手に閉じられる?」「ストリームを開いたままにして後で再利用したい」――本記事はその疑問を明確に解消し、現場で迷わないための安全な実装パターンと落とし穴回避策を、.NET Framework 4.0/4.8(WinForms)前提で徹底解説します。

目次

結論(最初に答え)

  • Image.FromStream(Stream, Boolean, Boolean) は渡されたストリームを自動では閉じません。
  • Using … End Using が破棄するのは Using で宣言した Image オブジェクトのみ(サンプルでは MyImage)。InputStream は開いたままです。
  • Image の生存中は、原則として元ストリームを閉じない(GDI+ の動作上、画像が内部的にストリームへ依存するため)。
    ストリームを早く閉じたい場合は、画像をクローン(New Bitmap(original))して依存を切るのが定石です。
  • 処理が済んだら、ストリームは自分で Close() または Dispose() を呼んで解放します。

対象と前提

対象は VB.NET / WinForms、.NET Framework 4.0 および 4.8。System.Drawing(GDI+)ベースの画像処理を想定しています。WPF の BitmapImage や .NET 6+ の System.Drawing.Common のクロスプラットフォーム事情は範囲外です。

なぜ Image.FromStream はストリームを閉じないのか

System.Drawing.Image は内部で GDI+ を利用しており、イメージデータの解凍やメタデータの参照を遅延的に行う可能性があります。つまり、コンストラクターや FromStream 呼び出し時点で全データを完全にメモリへコピーしない場合があり、その間は元のストリームが有効であることが前提になります。したがってフレームワーク側は「あなたが渡したストリームを勝手に閉じる」ことはしませんし、できません。

Using が解放するのはだれか

Using は IDisposable を実装する「自分自身」を確実に破棄する構文です。次のコードでは破棄対象は MyImage のみであり、InputStream ではありません。

Using MyImage As Image = Image.FromStream(InputStream, False, False)
    ' 画像処理
End Using ' ここで破棄されるのは MyImage(Image)だけ。InputStream は開いたまま

Image.FromStream の引数(2 つの Boolean)の正しい理解

第 2, 第 3 引数は「ストリームを閉じる・閉じない」を制御するものではありません。

引数型意味ポイント
useEmbeddedColorManagementBoolean画像に埋め込まれたカラープロファイル(ICC プロファイル等)を使用するか色再現性が重要なら True。処理コストはわずかに増加
validateImageDataBoolean読み込み時に画像データを検証するか(壊れたデータを早期検出)信頼できない入力なら True で堅牢性向上。完全読み込みが促進されることもあるが、ストリーム非依存になる保証ではない

代表的なコードパターンとベストプラクティス

① ストリームを「意図的に」開いたままにする(もっともシンプル)

質問のケース。Image は Using 内だけで使い、ストリームは後で活用(または自前でクローズ)します。

Dim InputStream As New IO.MemoryStream(IO.File.ReadAllBytes("sample.jpg"))

Using MyImage As Image = Image.FromStream(InputStream, False, False)
' ここで画像が使える
MessageBox.Show(MyImage.Width.ToString())
End Using ' 画像だけ破棄。InputStream は開いたまま

If InputStream.CanRead Then
MessageBox.Show("ストリームはまだ開いています")
End If

' 後片付け(必要になった時点で)
InputStream.Close()
InputStream.Dispose()

② 画像をクローンして、ストリーム依存を完全に断ち切る

画像を長期間保持する/別スレッドに渡す/ファイルロックをすぐ解放したい――そんなときは クローンが有効です。

Dim bmp As Bitmap
Using fs As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
    Using img As Image = Image.FromStream(fs, False, False)
        ' クローンして GDI+ のストリーム依存を切る
        bmp = New Bitmap(img)
    End Using ' img の破棄。fs はまだ Using 内
End Using ' fs をここで閉じても bmp は利用可能

' 以降、bmp は元ストリーム不要(自由に使用・保存可能)
bmp.Save("out.png", Imaging.ImageFormat.Png)
bmp.Dispose()

ポイント: New Bitmap(img) はピクセルデータを新しい Bitmap にコピーするため、元のストリームや img のライフサイクルから独立します。

③ 「両方」Using に入れるネスト構文(片付け順序を明確に)

ストリームも一緒に Using に入れておけば、画像 → ストリーム の順に破棄されるため、後片付けの順序ミスを防げます。

Using InputStream As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
    Using MyImage As Image = Image.FromStream(InputStream, False, False)
        ' 画像処理
    End Using   ' 先に画像を破棄
End Using       ' 次にストリームを破棄

④ よくある失敗例(ファイルをすぐ閉じてしまう)

' これは避ける:画像がまだ生きているのにファイルを閉じる
Dim fs As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read)
Dim img As Image = Image.FromStream(fs)
fs.Close() ' <= ここで閉じると、後の操作で例外(GDI+ 一般エラー等)が起きうる
' img を使う処理...
img.Dispose()

「動くこともある」ため見逃されがちですが、環境や画像フォーマット、後続操作(Save/RotateFlip/サムネイル生成など)によって失敗します。再現性のある安全策=クローンを採用しましょう。

検証に使える最小サンプル(WinForms)

以下は「Using を抜けても InputStream は開いている」ことを確認できる実用的サンプルです。

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim input As New IO.MemoryStream(IO.File.ReadAllBytes("sample.jpg"))


Using img As Image = Image.FromStream(input, False, False)
    Me.Text = $"幅={img.Width}, 高さ={img.Height}"
End Using ' ← ここで解放されるのは img だけ

' まだ開いているので再度読み取れる
If input.CanSeek Then input.Seek(0, IO.SeekOrigin.Begin)
Dim head As Byte() = New Byte(7) {}
input.Read(head, 0, head.Length)
MessageBox.Show($"先頭 8 バイト: {BitConverter.ToString(head)}")

' 明示的に片付け
input.Dispose()


End Sub

「ファイルロック」「GDI+ 一般エラー」の正体と対策

  • ファイルロック: FileStream を開いたまま画像を保持すると、元ファイルがロックされ、上書き・削除で失敗します。
    対策: 早期にクローン(New Bitmap(img))→ 元ストリームを閉じる。
  • GDI+ 一般エラー: 典型的には「保存先が同じファイルでロック中」「元ストリームを閉じた/破棄した」「保存先フォルダへの権限不足」。
    対策: 別ファイル名で保存する、クローンしてから保存する、権限を確認する。

パフォーマンス/メモリ設計の勘所

  • クローンはメモリを消費: 1920×1080・32bpp の場合、ピクセルだけで約 7.9MB。複数枚同時にクローンする設計ではメモリ監視が必須。
  • 一時的に MemoryStream に載せ替えるコスト: File.ReadAllBytes はファイルを丸ごとメモリに読み込むため、大容量画像や連続処理では GC プレッシャーが増えます。
    可能なら FileStream 直読み+クローン→即クローズが安定。
  • サムネイルだけ欲しい場合: クローン後に GetThumbnailImage や DrawImage で縮小する。元ファイルを早く解放できる。

ストリーム種別ごとの注意点

ストリーム種別主な用途注意点推奨パターン
FileStreamディスク上の画像ファイルロックが発生。上書き保存や削除に影響読み込んだら即クローン → すぐ閉じる
MemoryStreamバイト配列からの読み込みメモリ消費が大きくなりがち必要な箇所でのみ生成。長生きさせない
NetworkStream 等ソケット/HTTP 等のストリーミング遅延読み込みでブロックし得る一度 MemoryStream にバッファしてから FromStream

安全に保存するためのテンプレート

「読み込んだそのファイルと同名で保存したい」場合の定石です。ダブルバッファリングでロックや失敗を回避します。

Dim tempPath = IO.Path.GetTempFileName()
Using fs As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
    Using img As Image = Image.FromStream(fs, False, True)
        Using bmp As New Bitmap(img) ' 依存を切る
            bmp.Save(tempPath, Imaging.ImageFormat.Jpeg)
        End Using
    End Using
End Using
' 置換(Windows のみ・必要ならバックアップ名を備える)
IO.File.Copy(tempPath, "sample.jpg", True)
IO.File.Delete(tempPath)

ユースケース別・実装クイックガイド

ユースケースベストプラクティス備考
単にサイズを読みたいUsing img ... End Using で済ませる。ストリームは必要なら後でクローズ高速・低メモリ
画像を UI に表示し続けたいクローンしてから PictureBox.Image = bmpファイル解放・スレッド安全性向上
読み込んだファイルへ上書き保存必ずクローン→元ストリームを閉じる→別名に保存→置換GDI+ 一般エラー回避
連続大量処理1 枚ずつ Using と Dispose を徹底。大きい画像は逐次的に解放ワーキングセット抑制

テスト観点とデバッグのコツ

  • 例外の明示化: 例外を握りつぶさず、キャッチしたら ex.ToString() をログへ。
  • ロック検知: 同じファイルの FileShare.None で開いてみて例外が出るか確認。
  • リーク検知: 長時間動作のツールで、処理件数に比例してメモリが増えないかを監視。
  • 破損画像の試験: validateImageData:=True 版もテストに含め、入力バリデーションを強化。

FAQ

  • Q: validateImageData:=True にすればストリームを閉じても平気?
    A: 検証が強くなるだけで、「ストリーム非依存」になる保証はありません。クローンが確実。
  • Q: New Bitmap(Stream) なら最初からストリーム非依存?
    A: Bitmap のストリームオーバーロードも原則は同じです。クローン(New Bitmap(img))で切り離すのが定石。
  • Q: PictureBox に直接 Image.FromFile を渡しているが大丈夫?
    A: Image.FromFile は内部でファイルをロックする挙動があり、上書き保存でトラブルの原因に。FromStream + クローンが安全です。
  • Q: 画像表示中にファイルを削除できない。
    A: どこかでファイルハンドルが生きています。表示用の Image をクローンしてから、元ストリーム・元画像を破棄してください。

実運用で使えるチェックリスト

  • 画像は Using で確実に破棄しているか。
  • ファイルを長時間ロックしていないか(必要なら即クローンして解放)。
  • 不正画像や壊れたファイルを想定した例外処理があるか。
  • 保存は別名一時ファイル → 置換の手順にしているか。
  • 処理後にストリームを Close/Dispose しているか。

まとめ

Image.FromStream は渡されたストリームを自動では閉じません。Using で破棄されるのは画像オブジェクトだけです。画像がストリームに依存する可能性があるため、画像の生存中に元ストリームを閉じるのは危険。ストリームを早く解放したいなら、画像をクローンして依存を断ち切るのが最良手です。最後は自分でストリームを確実に後片付け――この 3 点を守れば、ファイルロックや GDI+ エラーに悩まされることは大きく減ります。

付録:コピペで使えるスニペット集

ファイル → 表示用画像(ロックしない)

Dim displayImage As Bitmap
Using fs As New IO.FileStream(path:="sample.jpg", mode:=IO.FileMode.Open, access:=IO.FileAccess.Read, share:=IO.FileShare.Read)
    Using img As Image = Image.FromStream(fs, useEmbeddedColorManagement:=False, validateImageData:=True)
        displayImage = New Bitmap(img) ' ロックを残さない
    End Using
End Using
PictureBox1.Image = displayImage

メモリ上の JPEG バイト列 → PNG へ変換保存

Dim jpegBytes As Byte() = IO.File.ReadAllBytes("in.jpg")
Using ms As New IO.MemoryStream(jpegBytes, writable:=False)
    Using img As Image = Image.FromStream(ms, False, True)
        Using bmp As New Bitmap(img)
            bmp.Save("out.png", Imaging.ImageFormat.Png)
        End Using
    End Using
End Using

壊れた画像を早期検出(バリデーション ON)

Try
    Using fs As New IO.FileStream("maybe_broken.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
        Using img As Image = Image.FromStream(fs, False, True) ' validateImageData:=True
            ' OK 時の処理
        End Using
    End Using
Catch ex As Exception
    MessageBox.Show("画像が不正です。" & Environment.NewLine & ex.Message)
End Try

キーメッセージ(おさらい)

  • 自動クローズはされない: FromStream はストリームを閉じない。
  • Using は自分だけ: 破棄されるのは Image 自体。
  • 生存中は閉じない: 画像がストリームへ依存しうる。
  • 早期解放はクローン: New Bitmap(img) で依存を断つ。
  • 最後は手で閉じる: InputStream.Close/Dispose を忘れない。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次