CodeGym /コース /C# SELF /ストリームのクローズとリソース解放 ( using

ストリームのクローズとリソース解放 ( using)

C# SELF
レベル 36 , レッスン 0
使用可能

1. C#でリソースを正しく解放するには?

OSを厳しい図書館司書だと思ってみて。君が本を借りた(ファイルやストリームを開いた)、読んでる、でも…返すの忘れた!司書が怒る:「なんでまだ本返してないの!?」ストリームも同じ。開いたままだとリソース(ファイルディスクリプタ、メモリの一部、他アプリからのファイルロック)をずっと使いっぱなしになる。

ストリームを閉じないと、「なんか動かない」から「全部壊れた、誰もこのファイルに書き込めない」まで色々な問題が起きる。未クローズのストリームが多いと、システムがリソースリークして動かなくなることもあるよ。

どこが危険?

  • ファイルが閉じられず、コマンドがディスクに届かない(例えば書き込み時、データがバッファに残ったまま)。
  • ファイルが他プロセスにロックされる — 同僚や他のプログラムがイライラする。
  • ディスクリプタの上限: Windows/Linuxではプロセスごとに開けるファイル/ストリーム数に制限がある。

IDisposableインターフェース

非管理リソース(ストリーム、ファイル、DB、ソケットなど)を扱うクラスは、IDisposableインターフェースを実装しなきゃダメ。


public interface IDisposable
{
    void Dispose();
}

Dispose()メソッドの中で、全部のかわいそうなリソースを解放するのが普通:ファイルをやっと閉じたり、コネクション切ったり、メモリを解放したり。

ストリームは、ファイルやコネクション、メモリなど大事なリソースを持ってるオブジェクト。閉じないと、ファイルがロックされたまま(Wordが"ファイルは他のアプリで開かれてます!"って言うやつ)、システムがメモリ不足になる。だから、使い終わったら必ずストリームを解放しよう。

.NETのストリームはIDisposableを実装してる。つまり、Dispose()を呼ぶ(またはusingブロックに入れる)必要があるってこと。

2. ストリームのクローズ方法:危険から安心まで

パターン1. 「手動」クローズ:やめとけ!

これは古いやり方。前のサンプルで見せたけど、今どきはほぼ誰も使わない :P


var stream = new FileStream("file.txt", FileMode.Open);
// ストリームで作業
stream.Close(); // または stream.Dispose()

問題点:開いてから閉じるまでの間にエラーや例外が起きたら、ファイルが開きっぱなしでロックされる。図書館でピザの匂いに釣られて本持ったまま飛び出す感じ…

パターン2. try...finallyを使う


FileStream stream = null;
try
{
    stream = new FileStream("file.txt", FileMode.Open);
    // ストリームで作業
}
finally
{
    if (stream != null)
        stream.Dispose();
}

このやり方は安心:finallyはエラーがあっても必ず実行される。でも、正直書くのめんどい。

パターン3. かっこよく安全:using演算子

クラシック構文(using ( ... ) { ... }


using (var stream = new FileStream("file.txt", FileMode.Open))
{
    // ストリームで作業
}
// ここでstream.Dispose()が自動で呼ばれる!

ポイントは、usingブロックの中でストリームを使い、ブロックが終わったら(例外があっても)ファイルが閉じること。

パターン4. モダン構文

モダン構文(using var


using var stream = new FileStream("file.txt", FileMode.Open);
// ストリームで作業
// ... 変数のスコープが終わったらDisposeが自動で呼ばれる

最高!余計なインデントや波カッコがいらない。

「中身」はどうなってる?

using演算子は、コンパイラがtry...finallyに展開してくれる。ジョーク:「usingは君の代わりにクリーンなコードを書く ― そのうちコーヒー飲みながらStack Overflow眺め始めるかも?」

クラシックとモダンusingの違い

クラシックusingブロック using var(宣言)
見た目
using (var x = ...) { ... }
using var x = ...; ...
スコープ ブロックの波カッコ内 今のブロック(メソッド、ループなど)の終わりまで
簡潔さ ちょっと冗長 シンプル、インデント少なめ
登場バージョン C# 1.0 C# 8.0以降

3. using宣言って何?

5年前、IDisposableオブジェクトを扱う新しいシンプルな方法が登場した。

using宣言は、ブロックじゃなくて変数宣言にusingを付けるやり方。変数は今のブロック(メソッドやループなど)の終わりで自動的に解放される。追加の波カッコブロックの終わりじゃないよ。


using var stream = new FileStream("file.txt", FileMode.Open);
// ストリームで作業
Console.WriteLine(stream.Length);

// ここではまだファイルは開いてる!

// ... メソッドの終わり
// stream.Dispose()がここで自動実行

クラシックusingとの主な違い:

  • 波カッコがいらない、ネストしたコードブロックができない。
  • 変数は宣言したブロックの終わりまで使える(普通はメソッド、時々ループやクラス(クラスレベルで宣言した場合))。
  • リソース解放はブロックの終わりでだけ起きる。

なぜこれがイケてる?

  • ネストが減る — コードがかなり短く読みやすくなる。
  • 複数リソースも楽勝using変数を連続で宣言すれば、メソッドの終わりで全部解放される。
  • ミスが減るDispose()を呼ぶべきスコープを間違えにくい。

4. 比較:クラシック vs. モダンusing

コードで比較してみよう。

クラシックなやり方


using (var reader = new StreamReader("input.txt"))
{
    using (var writer = new StreamWriter("output.txt"))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            writer.WriteLine(line.ToUpper());
        }
    }
} // ここで両方のファイルが閉じられる

モダンなやり方(C# 8+)


using var reader = new StreamReader("input.txt");
using var writer = new StreamWriter("output.txt");

string line;
while ((line = reader.ReadLine()) != null)
{
    writer.WriteLine(line.ToUpper());
}
// メソッドを抜けると両方のファイルが閉じられる

シンプルでしょ? ネストがもっと深くなる場合は特に、モダンなやり方がめっちゃ楽。

5. Dispose()はいつ・どこで呼ばれる?

ここでよく学生がミスる:Disposeは使った直後に呼ばれると思いがち ― でも違うよ!

この例を見てみて:


void MyMethod()
{
    using var fileStream = new FileStream("data.bin", FileMode.Open);
    // ... たくさんのコード、ループやネストした呼び出しもあるかも
    // fileStreamはまだ開いてる!

    // ここでfileStreamにアクセスできる
}
// ここ、メソッドの}でfileStream.Dispose()が呼ばれる

大事なポイント: ループ内でusing変数を宣言すると、Disposeは各イテレーションの後に呼ばれる。


foreach (var path in filePaths)
{
    using var reader = new StreamReader(path);
    // readerで作業
} // 各イテレーション後にreader.Dispose()(ファイルを閉じる)

6. 古いコードを移植するときのミス

たまに、古いコードを移したりクラシックusingのサンプルをコピペしたりする時、変数の「寿命」が波カッコのスコープより長く必要な場合がある。そんな時はクラシックなやり方は向かないけど、using宣言ならバッチリ。

でも注意点もある。例えば、ループ内で2つのリソースがあって、一方はもう一方より長く「生きて」ほしい場合は、宣言の順番に気をつけて:


using var resource1 = ...;
for (int i = 0; i < 10; i++)
{
    using var resource2 = ...;
    // resource2は1イテレーションだけ生きる
    // resource1は関数全体で生きる
}

7. 実践

前回から作ってる学習用アプリ ― 小さなコーヒー注文シミュレーターを進化させよう。今度は注文履歴をテキストファイルに保存&起動時に読み込めるようにしよう。

ステップ1: 注文をファイルに保存


using var writer = new StreamWriter("orders.txt", append: true);
writer.WriteLine("コーヒー: ラテ; ミルク: オーツ; サイズ: ラージ");

// StreamWriterの2番目の引数append: trueは、ファイルを上書きじゃなく追記したいって意味。

ステップ2: 注文履歴を読む


using var reader = new StreamReader("orders.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"注文: {line}");
}

このメソッドが終われば、ファイルは自動で閉じられるよ。

8. using宣言のベストプラクティス

1. IDisposableを実装してるオブジェクトには必ずusingを使おう
.NETのファイル・ストリーム・リソース系クラスはほとんどこれ。つまり「usingで解放してね!」ってサイン。

2. スコープに注意:必要なとこだけでusing-varを宣言しよう
変数が数行だけ必要なら、その場で宣言して早めに解放しよう。

3. 解放順序を忘れずに
複数のusing変数を連続で宣言すると、Disposeは逆順で呼ばれる:


using var first = new Resource("First");
using var second = new Resource("Second");
// ... 作業
// 先にsecondのDispose、次にfirstのDispose

一方がもう一方に依存してる場合(例:書き込みストリームはファイルより先に解放すべき)には大事。

4. メソッド外でusing宣言は使わない
using宣言はクラスレベル(フィールドなど)では禁止。メソッドやコンストラクタなどの中だけで使える。

5. エラー処理と組み合わせよう
usingだけじゃ例外を全部カバーできないこともあるから、読み書きエラーをちゃんと制御したいならtry-catchも使おう。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION