C# using の3形態 — using ステートメント / using 宣言 / await using で業務SE が踏む使い分け
みなさんこんにちは!ヒロポンです!!
using (var conn = new SqlConnection(...))、業務 SE なら毎日のように書いてるやつ。
でもさ、なんで using を付けてるんだっけ?? ブロック抜けたら自動で Dispose が呼ばれる、そこまでは多分わかってる。
じゃあ C#で増えた using 宣言 (using var conn = ...;) と await using、この違いを 1 文で言える? ん? 詰まったらこの記事の出番。
副業や転職で .NET 6+ のコードを開いた瞬間、ここで止まる業務 SE はマジで多い。俺も最初に using var 見た時、「セミコロンで終わってる using って何?」で 30 分固まった口です。
この記事では C# の using 3 形態 (ステートメント / 宣言 / await using) の使い分けを、try-finally への展開・例外時の挙動・スコープの違いまで、コピペで動くコードと比較表で整理する。
忙しい人向けに最初にまとめ
- using には ステートメント (C# 1.0+) / 宣言 (C# 8+) /
await using(C# 8+ / IAsyncDisposable) の 3 形態がある - どれも内部的には try-finally + Dispose に展開される・スコープと書きやすさが違うだけ
- .NET Framework 4.7.2 (C# 7.3) は宣言形不可・副業/転職で .NET 6+ に触れる時に「これ何?」になりがち
- ⏱ 対処目安: 30 分で 3 形態の挙動と落とし穴を頭に入れられる
- 同僚や後輩から「
using varって何が違うんですか?」と聞かれて答えられないと、業務側の信頼回復より先輩としての面目が崩れるので潰しておく
そもそも using とは何をしているのか
C# の using ステートメント・宣言は、IDisposable を実装したオブジェクトを確実に Dispose するための糖衣構文。
内部的には try-finally に展開される。これだけ。
📖『Effective C#』(第5章 例外処理) ではこう書かれている:
メソッド中で IDisposable オブジェクトを使用する場合、そのオブジェクトを確実に破棄するには using ステートメントを使用する方法が最も簡単です。using ステートメントを使用すると、ステートメント内で生成されるオブジェクトを囲うように try…finally ブロックが用意されます。次の2つのコードからは同じ IL が生成されます。
言い換えるとこう。以下 2 つは同じ IL に展開される:
// 形① using ステートメント
using (var reader = new StreamReader("data.txt"))
{
var line = reader.ReadLine();
Console.WriteLine(line);
}
// 形② try-finally で手書き (同じ IL)
StreamReader reader = null;
try
{
reader = new StreamReader("data.txt");
var line = reader.ReadLine();
Console.WriteLine(line);
}
finally
{
if (reader != null) ((IDisposable)reader).Dispose();
}
書きやすさが違うだけで挙動は完全に同じ。例外が出ても finally で確実に Dispose が呼ばれる。
IDisposable は超シンプルなインターフェイス
📖 同書 (第2章 リソース管理) より:
public interface IDisposable { void Dispose(); }
Dispose() メソッド 1 つだけ。
SqlConnection / StreamReader / FileStream など、OS リソース (ハンドル・接続・ファイル) を持つ全クラスは IDisposable を実装してる。これを忘れるとリソースリーク → 本番で「接続プール枯渇」「ファイルロック残存」で詰む。
業務 SE が踏む 3 形態の使い分け
順に書くとこう。
- 形態①: using ステートメント (C# 1.0+ / 全環境で使える)
- 形態②: using 宣言 (C# 8+ / 関数末尾まで自動 Dispose)
- 形態③: await using (C# 8+ / IAsyncDisposable / 非同期解放)
3 つとも目的は同じ「Dispose を確実に呼ぶ」。違いはスコープと非同期対応だけ。
形態①: using ステートメント (C# 1.0+ 全環境 OK)
レガシー WinForms / .NET Framework 4.7.2 の現場で見るやつ。C# 1.0 から使えて、ブロックスコープが明確。
using System;
using System.Data.SqlClient;
class Program
{
static void Main()
{
// ブロックスコープで Dispose
using (var conn = new SqlConnection("Server=...;Database=...;Trusted_Connection=True;"))
{
conn.Open();
Console.WriteLine($"State: {conn.State}");
// この } を抜けた瞬間に Dispose が呼ばれる
}
// conn はここでアクセス不可 (スコープ外)
Console.WriteLine("Dispose 後");
}
}
実行結果:

特徴:
- C# 1.0+ で使える (.NET Framework 1.1 以降の全環境)
- ブロック
{ }でスコープが明確 }を抜けた瞬間に Dispose- 例外が出ても finally で確実に Dispose
- 入れ子で書くとピラミッド構造になって読みにくくなる ← 形態② が解決する課題
try-finally との等価性 (diff で確認)
「using って結局 try-finally じゃん」と思う人向けに、同じ意味のコードを diff で並べる。

Effective C# でも「同じ IL が生成される」と明言されてる。
つまり書き方が短いだけで挙動は完全に同じ。業務 SE 視点だと「読みやすさのために using 書く」で OK。
形態②: using 宣言 (C# 8+ / 関数末尾まで自動 Dispose)
C# 8 (2019 年・.NET Core 3.0+ / .NET 5+) で追加。using の後に (...) を書かず、セミコロンで終わる。
関数末尾まで自動で Dispose される、ってのがミソ。
using System;
using System.Data.SqlClient;
class Program
{
static void Main()
{
// 関数スコープで Dispose
using var conn = new SqlConnection("Server=...;Trusted_Connection=True;");
conn.Open();
Console.WriteLine($"State: {conn.State}");
// ... 関数末尾まで conn は生きてる
DoWork(conn);
} // ここで Main を抜ける瞬間に conn.Dispose() が呼ばれる
}
実行結果:

特徴:
- C# 8+ 専用 (.NET Framework 4.7.2 / 4.8 では使えない・.NET Core 3.0+ / .NET 5+ のみ)
- インデントが 1 段浅くなる (ピラミッド回避)
- スコープは関数末尾まで
- 例外時も同じく finally で Dispose
using 宣言の落とし穴: スコープが関数末尾になる
これ、関数が長いと変数スコープが分かりにくくなるんよ。こんなコードに遭遇すると詰まる。
static void ProcessOrders()
{
using var conn = new SqlConnection(connStr);
conn.Open();
// ... 50 行ほど何かの処理 ...
using var cmd = conn.CreateCommand();
cmd.CommandText = "UPDATE Orders SET ...";
// ... さらに 30 行 ...
// ここで関数終わり → conn と cmd が一気に Dispose される
// ★ 順序: cmd が先・conn が後 (宣言と逆順で Dispose)
}
俺の現場でやられたやつ。using var を関数末尾でぜんぶ Dispose する流れになってて、cmd と conn の Dispose 順序が逆だと接続プール詰まる、ってパターンで本番夜中に起こされた。
SqlConnection を Dispose する前に SqlCommand を Dispose しないと挙動がおかしくなるケースがある。マジでタチ悪い。
回避策: スコープを明確にしたい時は形態① (ブロック) に戻す、もしくは関数を分割する。
形態③: await using (C# 8+ / IAsyncDisposable)
非同期解放が必要なリソース (DbContext / NetworkStream / Pipeline 系) を非同期で解放するための形。これも C# 8+。
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
class Program
{
static async Task Main()
{
// IAsyncDisposable を実装した DbContext を非同期 Dispose
await using var db = new MyDbContext();
var orders = await db.Orders.ToListAsync();
Console.WriteLine($"取得件数: {orders.Count}");
} // ここで await db.DisposeAsync() が呼ばれる
}
実行結果:

特徴:
- C# 8+ かつ IAsyncDisposable を実装した型のみ
- 通常の
Dispose()ではなくDisposeAsync()が await される - DB 接続のクローズ通信・ネットワークソケットの shutdown 等で I/O 待ちが発生する場合に使う
- 通常の
usingで IAsyncDisposable オブジェクトを書くと警告 CS0840 が出る (CA2007 関連)
いつ await using を使う?
判断は単純。IAsyncDisposable を実装してるなら await using、そうでなければ using。EF Core の DbContext / IAsyncEnumerable / .NET 6+ の HttpClient 系などが対象。
書き間違いを防ぐコツは IDE 任せでいい。VS / Rider で IDisposable だけ実装してる型に await using を書くとエラー、逆に IAsyncDisposable 実装してる型に using を書くと「await using にしませんか」と警告が出る。
3 形態 × 5 観点 比較表
3 形態の使い分けを 1 枚にまとめる。


業務 SE 視点だと所属現場の .NET バージョンで選択肢が決まる。.NET Framework 4.7.2 メインなら形態① 一択、.NET 6+ 新規案件なら形態② を主軸 + 必要時に形態③。
罠①: 例外が出たら Dispose は呼ばれない?
「using の中で例外が出たら Dispose は呼ばれないんじゃない??」と聞かれることが多い。
答えは「呼ばれる」。内部的に try-finally に展開されてて、例外が出ても finally で Dispose が走るから。
Effective C# 第5章でも「ステートメント内で生成されるオブジェクトを囲うように try…finally ブロックが用意される」と明言されてる。
ところが以下のケースは Dispose が呼ばれない:
- using の
( )内 (オブジェクト生成中) で例外が出た場合 (= まだ try 句に入ってない) - コンストラクタ内で例外 (オブジェクト自体が完成してない)
- プロセス強制終了 (
Environment.FailFast/kill -9/ 電源断)
最初の 2 つは「まだリソース確保してないから Dispose 不要」という設計。最後はそもそも GC も動かないので諦める領域。
罠②: 複数 IDisposable の冗長性 — using ネスト or try-finally 手書きか
📖『Effective C#』(第5章) では複数オブジェクトを扱う時のヒントもある:
using ステートメントそれぞれに対して try…finally ブロックが生成されます。幸いなことに、IDisposable を実装している 2 つの別々のオブジェクトを 1 つのメソッド内で生成することは滅多にありません。上のコードの場合、正しく機能するためこのままで問題ありません。とはいえ、このコードは冗長であるため、複数の IDisposable オブジェクトを同じブロック内で生成するような try…finally ブロックを自分で用意した方がよい場合もあるでしょう。
つまり「using ネスト 3 段以上になったら try-finally 手書きを検討」。C# 8+ なら形態② (using 宣言) で 1 段浅くする選択もあり。
入れ子の解消パターン
// ❌ ピラミッド (using ネスト 3 段)
using (var conn = new SqlConnection(connStr))
using (var cmd = conn.CreateCommand())
using (var reader = cmd.ExecuteReader())
{
while (reader.Read()) { /* ... */ }
}
実は C# 1.0+ でも上の書き方は OK で、これは「括弧省略」として認められてる。ピラミッド回避のテクニック。
C# 8+ ならもっと短く:
// ✅ using 宣言 (C# 8+)
using var conn = new SqlConnection(connStr);
using var cmd = conn.CreateCommand();
using var reader = cmd.ExecuteReader();
while (reader.Read()) { /* ... */ }
罠③: using 宣言の Dispose 順序は宣言の逆順
形態② の落とし穴をもう一段掘り下げる。
複数の using var がある時、Dispose は宣言の逆順で呼ばれる。
static void Main()
{
using var a = new ResourceA(); // 1番目に作る
using var b = new ResourceB(); // 2番目に作る
using var c = new ResourceC(); // 3番目に作る
// 関数末尾:
// ① c.Dispose()
// ② b.Dispose()
// ③ a.Dispose()
} // ← この順で Dispose
業務 SE がハマるのが、SqlConnection と SqlCommand の順序。conn を先に宣言して cmd を後に宣言すれば、関数末尾で cmd.Dispose() → conn.Dispose() の順になって安全。
これを逆にすると、cmd が死んだ conn に対して Dispose を呼ぼうとしておかしくなる場面がある。
俺もこれで本番夜中に「接続プール枯渇 → 全 API が 500 連発」のトレースで 2 時間溶かしたことがある。マジでやらかした。
業務 SE が using で迷ったら見るフロー
ここまでの判断軸を 1 枚にまとめる。

動作確認メモ: ここで紹介した C# コードは .NET 8 SDK の Docker container でビルド + 実行確認済 (using 3 形態すべての Dispose 呼び出しログが出ることを検証)。
.NET Framework 4.7.2環境で形態② / ③ を書くと CS8370 (Feature 'using declarations' is not available in C# 7.3.) で構文エラーになる。所属現場の<LangVersion>を.csprojで確認しておくのが定石。
俺の現場メモ
数年前、物流系の基幹システム保守をやってた時の話。
.NET Framework 4.7.2 メインの現場で、副業で受けた .NET 6 のコードを開いた瞬間、using var conn = ...; を見て手が止まった。
「これ、セミコロンで終わってる using って何??」で 30 分検索した記憶。
結局 C# 8 で増えた using 宣言 だとわかったんやけど、当時の俺は .NET Framework 4.7.2 の現場知識しか持ってなかったので、新文法を見ただけで「これは違う言語か?」って一瞬本気で思った。マジで未熟。
しかも同僚から「await using db = new DbContext(); って何?」とも聞かれて、「あ、EF Core の DbContext は IAsyncDisposable なんで…」って説明できず、翌日「すみません、ちゃんと調べてきました」って謝った経験もある。
技術的にはこれで解決。
でも一番きつかったのは復旧時間より、後輩から先輩としての面目が一段落ちた瞬間。こっちの方が重い、ってのは業務 SE やってる人ならわかると思う。
その後 3 形態を頭に入れてからは、副業先で using var を見ても瞬時に「あ、C# 8 宣言形ね」って判定できるようになった。30 分の投資が、副業/転職市場での読みやすさ確保に効いた、って感じ。
まとめ
ここまでで C# using 3 形態の使い分けは揃った。要点を並べると:
- using には ステートメント (C# 1.0+) / 宣言 (C# 8+) / await using (C# 8+) の 3 形態
- 内部的にはすべて try-finally + Dispose に展開・例外時も Dispose 呼ばれる
- .NET Framework 4.7.2 メインの現場は形態①一択・.NET 6+ 新規案件は形態②主軸+IAsyncDisposable で形態③
- 入れ子 3 段以上なら形態② か try-finally 手書きで読みやすく
- using 宣言の Dispose 順序は宣言の逆順・SqlConnection と SqlCommand の順序ミスに注意
同僚や後輩から「using var って何?」と聞かれて答えられるかどうかは、業務 SE の年次が上がるほど効いてくる。
ここで詰まると後輩から「先輩、こんなこと知らないんすか」って言われて、朝礼後の信頼回復に丸 1 日かかるやつ。30 分で潰せる論点なので、平時に押さえておくのが筋。
こんな感じで使い分けを 1 周頭に入れておけば、.NET バージョン違いの現場を渡り歩いても迷わない。
よくある質問
Q1. using を書き忘れて Dispose し忘れたらどうなる?
A. ファイナライザ (デストラクタ) が GC のタイミングで呼ばれて Dispose 相当の処理が走るが、タイミングが不定。接続プールが解放されるまで数秒〜数分のラグが出て、高負荷時に「接続プール枯渇」や「ファイルロック残存」を引き起こす。IDisposable を持つオブジェクトは using 書く、が鉄則。
Q2. C# 8 の using 宣言を .NET Framework 4.8 で使えますか?
A. 使えません。using 宣言は C# 8 言語機能で、C# 8 を完全サポートするのは .NET Core 3.0+ / .NET 5+ のみ。.NET Framework 4.x は C# 7.3 まで。csproj で <LangVersion>8.0</LangVersion> を指定しても一部機能だけ動く / 動かないケースがあるので、.NET Framework 案件では形態① (ステートメント) に統一するのが安全。
Q3. using の中で例外が出たら Dispose は呼ばれますか?
A. 呼ばれます。using は内部的に try-finally に展開されるため、try 句内で例外が出ても finally 句で確実に Dispose が走る。ただし「using の ( ) 内 (オブジェクト生成中) の例外」「コンストラクタ内の例外」はオブジェクトがまだ完成していないので Dispose 不要 (というか呼べない)。
Q4. await using と using を間違えて書いたらどうなる?
A. IAsyncDisposable のみ実装してる型に using を書くと、警告 (CA2007 / CS0840 系) が出るがコンパイル自体は通るケースがある。とはいえ Dispose() が呼ばれて DisposeAsync() が呼ばれないので、非同期解放が必要な処理 (DB 接続のクローズ通信等) がスキップされる。IAsyncDisposable 実装型にはかならず await using を使う。
Q5. 業務 SE が using var を見たら、すぐ何をすべきですか?
A. 3 ステップで頭を切り替える。①「これは C# 8 の using 宣言だな」→ ②「この変数は関数末尾まで生きる」→ ③「Dispose は関数末尾で宣言の逆順で呼ばれる」。この 3 ステップを 30 秒で回せれば、副業/転職先のコードレビューでも詰まらない。
関連記事
- C# 例外処理の正解 — try-catch-finally / using / Exception フィルタ (when句) の使い分け — using と並ぶ Exception フィルタ 4 パターン (when 句 / Exception 型階層 / try-catch 入れ子 / finally) を一段詳しく見たい人向け
- C# ファイルIO の正解 — StreamReader / File.ReadAllLines / File.ReadLines / using の使い分け — StreamReader と using の組み合わせで「コピペで動く」ファイル読み込みパターンを揃えたい人向け
以上!
同じ罠でハマってる人いたら、どんどんシェア待ってるぜ!!
執筆者
バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


コメント