C# アプリ終了の正しい方法|Application.Exit・Close・Environment.Exitの違いと使い分け
はじめに
C#でアプリを終了する方法として、Windows Formsでは主にApplication.Exit()、Form.Close()、Environment.Exit()の3つが使われます。
どれも画面上では「アプリが閉じる」ように見えますが、終了対象や終了処理の流れは異なります。適切に使い分けないと、次のような問題が発生します。
サブフォームを閉じたかっただけなのに、アプリ全体が終了する
終了確認を表示しているのに、確認せず強制終了する
保存処理やリソース解放が実行されない
ウィンドウが消えてもプロセスが残る
閉じたフォームを再表示しようとして例外が発生する
この記事では、C#アプリ終了時に使われる3つの方法について、仕組み、コード例、注意点、適切な使い分けを解説します。
1. C#でアプリを終了する3つの方法と基本的な違い
1-1. Application.Exit・Form.Close・Environment.Exitの比較表
| 方法 | 主な終了対象 | 終了イベント | 強制力 | 主な用途 |
|---|---|---|---|---|
this.Close() | 現在のフォーム | FormClosing、FormClosed | 低い | フォームを通常どおり閉じる |
Application.Exit() | Windows Formsアプリ内のフォームとメッセージループ | FormClosing、FormClosed | 中程度 | 複数フォームを含むアプリ全体を終了する |
Environment.Exit(code) | 実行中のプロセス全体 | 通常の終了処理を保証しない | 高い | 継続不能な状態から直ちに終了する |
Application.Exit()とForm.Close()はWindows Forms向けの機能です。一方、Environment.Exit()はWindows Formsに限定されず、コンソールアプリなどを含む.NETプロセス全体を終了します。
1-2. 「フォームを閉じる」と「アプリを終了する」の違い
Windows Formsアプリでは、フォームとアプリケーションは同じものではありません。
たとえば、メインフォームから設定画面を開いている場合、設定画面でthis.Close()を実行すると、閉じるのは設定画面だけです。メインフォームは残るため、アプリも引き続き動作します。
一方、メインフォームをClose()で閉じると、通常はApplication.Run(new MainForm())によって開始されたメッセージループが終了し、結果としてアプリも終了します。
C#[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
この構成では、MainFormがスタートアップフォームです。そのため、MainFormを閉じることがアプリ終了につながります。Microsoftのドキュメントでも、スタートアップフォームを閉じた場合はアプリケーションが終了すると説明されています。Microsoft Learn
ただし、独自のApplicationContextを使用している場合などは、メインフォームを閉じても必ずアプリが終了するとは限りません。終了条件は、アプリのメッセージループをどのように管理しているかによって変わります。
1-3. 終了方法を選ぶときの判断基準
終了方法は、次の順番で検討すると判断しやすくなります。
現在のフォームだけを閉じたい場合は、Close()を使用します。通常のWindows Formsアプリでメインフォームから終了する場合も、まずはClose()を検討します。
複数のフォームをまとめて閉じ、Windows Formsアプリ全体を終了したい場合は、Application.Exit()が候補です。
通常の終了処理を待たず、プロセスを直ちに停止しなければならない場合に限り、Environment.Exit()を使用します。
基本的には、強制力の弱い方法から選ぶのが安全です。
Close
↓
Application.Exit
↓
Environment.Exit
2. Application.ExitでWindows Formsアプリ全体を終了する
2-1. Application.Exitの仕組みと終了対象
Application.Exit()は、Windows Formsアプリで実行されているメッセージループに終了を通知し、開いているアプリケーションウィンドウを閉じるメソッドです。
C#Application.Exit();
呼び出されると、Application.OpenFormsで管理されている各フォームに対してFormClosingイベントが発生します。いずれかのフォームが終了をキャンセルしなければ、FormClosedイベントが発生し、フォームとメッセージループが閉じられます。Microsoft Learn
ただし、Application.Exit()はOSプロセスを無条件に強制終了する命令ではありません。Windows Formsのメッセージループとウィンドウを終了させるためのメソッドであり、別のフォアグラウンドスレッドが動作し続けている場合は、プロセスが残ることがあります。
2-2. 終了ボタンからApplication.Exitを呼び出すコード例
終了ボタンからアプリ全体を閉じる基本的なコードは次のとおりです。
C#private void exitButton_Click(object sender, EventArgs e)
{
Application.Exit();
}
実際のアプリでは、終了前に確認ダイアログを表示することが一般的です。
C#private void exitButton_Click(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show(
"アプリを終了しますか?",
"終了確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
);
if (result == DialogResult.Yes)
{
Application.Exit();
}
}
複数のフォームを開く可能性があり、どの画面の終了ボタンからでもアプリ全体を閉じたい場合に使用できます。
2-3. FormClosingイベントが発生するタイミング
Application.Exit()を呼び出すと、開いている各フォームでFormClosingイベントが発生します。
そのため、未保存データの確認や終了のキャンセルを実装できます。
C#private bool hasUnsavedChanges = true;
private void MainForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
if (!hasUnsavedChanges)
{
return;
}
DialogResult result = MessageBox.Show(
"保存されていない変更があります。終了しますか?",
"終了確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
if (result == DialogResult.No)
{
e.Cancel = true;
}
}
e.Cancel = trueを設定すると、そのフォームの終了が取り消されます。Application.Exit()では、いずれかのフォームが終了をキャンセルすると、それ以上の終了処理を中止して戻ります。Microsoft Learn
終了理由はe.CloseReasonで確認できます。
C#private void MainForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.ApplicationExitCall)
{
// Application.Exit()による終了
}
}
代表的な終了理由には、次の値があります。
UserClosing:ユーザー操作やClose()による終了ApplicationExitCall:Application.Exit()による終了WindowsShutDown:Windowsのシャットダウンによる終了TaskManagerClosing:タスクマネージャーなどからの終了
終了経路に応じて処理を分けたい場合に利用できます。
2-4. Application.Exitが適しているケース
Application.Exit()が適しているのは、次のようなケースです。
複数のモデルレスフォームを開くアプリ
サブフォームからアプリ全体を終了したい
タスクトレイ常駐アプリを終了したい
複数のWindows Formsメッセージループをまとめて終了したい
開いている各フォームの
FormClosing処理を通して終了したい
たとえば、メインフォームを非表示にしてタスクトレイへ格納するアプリでは、this.Close()だけでは目的の終了動作にならないことがあります。その場合、タスクトレイメニューの「終了」からApplication.Exit()を呼び出す設計が考えられます。
C#private void exitToolStripMenuItem_Click(
object sender,
EventArgs e)
{
notifyIcon.Visible = false;
Application.Exit();
}
2-5. Application.Exitを呼んでも終了しない原因
Application.Exit()を呼び出してもC#アプリが終了しない場合は、主に次の原因が考えられます。
FormClosingで終了がキャンセルされている
いずれかのフォームでe.Cancel = trueが設定されると、アプリ終了は中止されます。
C#private void Form1_FormClosing(
object sender,
FormClosingEventArgs e)
{
e.Cancel = true;
}
常にtrueを設定していないか、終了確認処理の条件が正しいかを確認しましょう。
フォアグラウンドスレッドが残っている
Threadクラスで作成したスレッドは、初期状態ではフォアグラウンドスレッドです。フォームとメッセージループが終了しても、そのスレッドが動作している間はプロセスが残る可能性があります。
C#private Thread? workerThread;
private void StartWorker()
{
workerThread = new Thread(() =>
{
while (true)
{
Thread.Sleep(1000);
}
});
workerThread.Start();
}
終了時にキャンセル要求を出してスレッドを停止し、必要に応じてJoin()で終了を待つ設計にします。
C#private readonly CancellationTokenSource cancellationTokenSource = new();
private void StartWorker()
{
workerThread = new Thread(() =>
{
while (!cancellationTokenSource.Token.IsCancellationRequested)
{
Thread.Sleep(100);
}
});
workerThread.Start();
}
private void MainForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
cancellationTokenSource.Cancel();
workerThread?.Join();
}
アプリが終了せず非表示になっている
FormClosingイベントでe.Cancel = trueを設定し、その後Hide()しているアプリでは、ウィンドウが見えなくなってもプロセスは動作し続けます。
C#private bool allowExit;
private void MainForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
if (!allowExit)
{
e.Cancel = true;
Hide();
}
}
private void ExitApplication()
{
allowExit = true;
Application.Exit();
}
タスクトレイ常駐アプリでは、「ウィンドウを閉じる操作」と「アプリを完全終了する操作」を明確に分けることが重要です。
3. Closeでフォームを安全に閉じる
3-1. Form.Closeの仕組みと終了対象
Form.Close()は、対象となるフォームを閉じるメソッドです。
C#this.Close();
基本的には、呼び出したフォームだけが終了対象です。フォームを閉じる前にFormClosing、閉じた後にFormClosedが発生します。
モデルレスフォームは通常、Close()によって破棄されます。フォームに関連付けられたリソースも解放されるため、同じインスタンスをそのまま再表示することはできません。Microsoft Learn+1
3-2. this.Closeで現在のフォームを閉じるコード例
現在表示しているフォームを閉じる場合は、次のように記述します。
C#private void closeButton_Click(object sender, EventArgs e)
{
this.Close();
}
thisは現在のフォームを表しています。省略して次のように書くこともできます。
C#private void closeButton_Click(object sender, EventArgs e)
{
Close();
}
終了確認を追加する場合は、FormClosingイベントにまとめると、終了ボタンだけでなく、タイトルバーの閉じるボタンから終了した場合にも同じ処理を適用できます。
C#private void MainForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
DialogResult result = MessageBox.Show(
"画面を閉じますか?",
"確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
);
if (result == DialogResult.No)
{
e.Cancel = true;
}
}
終了ボタン側はClose()を呼ぶだけにします。
C#private void closeButton_Click(object sender, EventArgs e)
{
Close();
}
これにより、終了確認処理が複数の場所へ重複するのを防げます。
3-3. メインフォームをCloseするとアプリも終了する理由
一般的なWindows Formsアプリは、次のようにメインフォームを指定して起動します。
C#Application.Run(new MainForm());
Application.Run()は、指定されたフォームを表示してメッセージループを開始します。指定されたメインフォームが閉じるとメッセージループが終了し、Application.Run()から制御が戻ります。
C#[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
// メインフォームが閉じた後に到達する
}
したがって、メインフォーム内でthis.Close()を呼ぶと、通常はアプリ全体の終了につながります。
ただし、別のフォアグラウンドスレッドが動作している場合や、独自のApplicationContextで終了条件を管理している場合は、フォームを閉じてもプロセスが残る可能性があります。
3-4. サブフォームだけを閉じる場合の使い方
サブフォームだけを閉じる場合は、サブフォーム自身でClose()を呼び出します。
C#public partial class SettingsForm : Form
{
public SettingsForm()
{
InitializeComponent();
}
private void closeButton_Click(object sender, EventArgs e)
{
Close();
}
}
メインフォームからサブフォームを表示するコードは次のようになります。
C#private void showSettingsButton_Click(
object sender,
EventArgs e)
{
var settingsForm = new SettingsForm();
settingsForm.Show();
}
この状態でSettingsFormのClose()を呼んでも、MainFormは閉じません。
サブフォームを閉じた後に確実に破棄したい場合は、usingとShowDialog()を組み合わせる方法が分かりやすいでしょう。
C#private void showSettingsButton_Click(
object sender,
EventArgs e)
{
using var settingsForm = new SettingsForm();
settingsForm.ShowDialog(this);
}
ShowDialog()で表示したフォームは、閉じる操作で非表示になるケースがあるため、不要になった段階でDispose()する必要があります。usingを使用すると、ダイアログを閉じた後に自動的に破棄できます。Microsoft Learn+1
3-5. CloseとHideの違い
Close()とHide()は、見た目が似ていても動作が異なります。
C#Close();
Close()はフォームを閉じます。モデルレスフォームでは通常、そのフォームが破棄されます。
C#Hide();
Hide()はフォームを非表示にするだけです。フォームのインスタンスは残るため、後からShow()で再表示できます。
C#private void hideButton_Click(object sender, EventArgs e)
{
Hide();
}
private void showButton_Click(object sender, EventArgs e)
{
Show();
}
使い分けの目安は次のとおりです。
| 目的 | 使用するメソッド |
|---|---|
| フォームが不要になった | Close() |
| 後で同じフォームを再表示したい | Hide() |
| アプリ全体を終了したい | メインフォームのClose()またはApplication.Exit() |
Hide()を多用すると、見えないフォームやリソースが残り続ける可能性があります。再表示する必要がないフォームは、Close()で終了させるのが基本です。
3-6. Close後にフォームを再表示できない場合の対処法
モデルレスフォームをClose()すると、通常はフォームが破棄されます。そのインスタンスへ再度Show()を実行すると、ObjectDisposedExceptionが発生する可能性があります。
C#private SettingsForm settingsForm = new();
private void closeSettingsButton_Click(
object sender,
EventArgs e)
{
settingsForm.Close();
}
private void reopenSettingsButton_Click(
object sender,
EventArgs e)
{
// すでに破棄されているため例外になる可能性がある
settingsForm.Show();
}
対処法は2つあります。
同じ状態のフォームを再表示したい場合は、Close()ではなくHide()を使用します。
C#private void SettingsForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
フォームを閉じるたびに状態を破棄してよい場合は、再表示時に新しいインスタンスを作成します。
C#private SettingsForm? settingsForm;
private void showSettingsButton_Click(
object sender,
EventArgs e)
{
if (settingsForm == null || settingsForm.IsDisposed)
{
settingsForm = new SettingsForm();
}
settingsForm.Show();
settingsForm.Activate();
}
すでに表示されている場合も考慮するなら、Visibleを確認します。
C#private void showSettingsButton_Click(
object sender,
EventArgs e)
{
if (settingsForm == null || settingsForm.IsDisposed)
{
settingsForm = new SettingsForm();
}
if (!settingsForm.Visible)
{
settingsForm.Show();
}
settingsForm.Activate();
}
4. Environment.Exitでプロセスを強制終了する
4-1. Environment.Exitの仕組みと終了コード
Environment.Exit(int exitCode)は、現在実行しているプロセスを終了するメソッドです。
C#Environment.Exit(0);
引数には、OSや呼び出し元へ返す終了コードを指定します。
一般的には、0を正常終了、0以外を異常終了やエラーとして扱います。
C#Environment.Exit(0); // 正常終了
Environment.Exit(1); // 一般的なエラー
Environment.Exit(2); // 別の種類のエラー
終了コードの意味はアプリ側で定義できます。バッチファイル、PowerShell、監視ツール、別プロセスなどからアプリを起動する場合、終了コードによって処理結果を判定できます。
4-2. Environment.Exit(0)を使用するコード例
終了ボタンからEnvironment.Exit(0)を呼び出すだけなら、次のように記述できます。
C#private void exitButton_Click(object sender, EventArgs e)
{
Environment.Exit(0);
}
ただし、通常のWindows Formsアプリの終了ボタンで、このコードを使用することは推奨されません。
Environment.Exit()はプロセスを直ちに終了するため、フォームの通常の終了処理や、後続コードが実行されることを前提にできないからです。
C#private void exitButton_Click(object sender, EventArgs e)
{
Environment.Exit(0);
// 実行されない
MessageBox.Show("終了しました");
}
4-3. 正常終了と異常終了で終了コードを使い分ける方法
終了コードを定数や列挙型として管理すると、意味が分かりやすくなります。
C#private enum ExitCode
{
Success = 0,
GeneralError = 1,
ConfigurationError = 2,
DatabaseConnectionError = 3
}
使用例は次のとおりです。
C#private void ValidateConfiguration()
{
if (!File.Exists("appsettings.json"))
{
Environment.Exit((int)ExitCode.ConfigurationError);
}
}
コンソールアプリであれば、可能な場合はMainメソッドから終了コードを返す方法がより自然です。
C#static int Main(string[] args)
{
bool success = RunApplication();
if (!success)
{
return 1;
}
return 0;
}
returnで終了できる場合は、処理が通常の制御フローに沿って進むため、Environment.Exit()より安全です。
4-4. finallyや後続処理が実行されない可能性
Environment.Exit()をtryブロックやcatchブロックから呼び出すと、対応するfinallyブロックは実行されません。Microsoftのドキュメントでも、Environment.Exit()による終了ではfinallyのコードが実行されないと明記されています。Microsoft Learn
C#try
{
Console.WriteLine("処理開始");
Environment.Exit(1);
}
finally
{
// 実行されない
Console.WriteLine("終了処理");
}
そのため、次のような処理をfinallyだけに任せている場合は注意が必要です。
ファイルやログの書き込み完了
データベース接続の終了
トランザクションの確定やロールバック
一時ファイルの削除
外部デバイスとの接続解除
設定や入力データの保存
ミューテックスなどの同期資源の解放
usingによるDispose()も通常のスコープ終了を前提としているため、Environment.Exit()を途中で呼び出した場合、その後の破棄処理に依存できません。
C#using var stream = new StreamWriter("log.txt");
stream.WriteLine("処理開始");
Environment.Exit(1);
// 通常のスコープ終了には到達しない
終了前に必要な処理がある場合は、可能な範囲で明示的に完了させてから終了します。
C#private void ExitWithError(string message, int exitCode)
{
try
{
File.AppendAllText(
"error.log",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}{Environment.NewLine}"
);
}
catch
{
// 終了直前なので、ログ保存失敗を必要に応じて無視する
}
Environment.Exit(exitCode);
}
ただし、終了直前の処理自体が停止したり例外を発生させたりする可能性もあります。Environment.Exit()を使用する設計では、保存処理を終了直前に集中させないことが重要です。
4-5. Environment.Exitを使用すべきケースと避けるべきケース
Environment.Exit()を検討できるのは、次のようなケースです。
アプリの状態が壊れており、通常処理を継続できない
複数のフォアグラウンドスレッドを含めて直ちに停止したい
コマンドラインツールから明示的な終了コードを返す必要がある
致命的な初期化エラーでアプリを開始できない
外部の監視プログラムへエラー終了を通知したい
反対に、次の用途では避けるべきです。
Windows Formsの通常の終了ボタン
サブフォームを閉じる処理
未保存データの確認が必要な場面
FormClosingで終了処理を行っているアプリfinallyやDispose()による後片付けが必要な場面単に処理を中断したいだけの場面
Environment.Exit()は、動作中の他のスレッドが存在していてもプロセスを終了します。通常の制御フローで終了できる場合は、Close()、Application.Exit()、returnなどを優先しましょう。Microsoft Learn
5. Application.Exit・Close・Environment.Exitの使い分け
3つの終了方法は、次のように使い分けます。
| 実現したいこと | 推奨する方法 |
|---|---|
| 現在のサブフォームだけを閉じる | this.Close() |
| メインフォームから通常終了する | this.Close() |
| 複数のフォームをまとめて終了する | Application.Exit() |
| タスクトレイ常駐アプリを完全終了する | Application.Exit() |
| 同じフォームを後から再表示する | Hide() |
| コンソールアプリを正常終了する | return |
| 終了コードを返して通常終了する | Mainからintを返す |
| プロセスを直ちに終了する | Environment.Exit(code) |
5-1. 通常のWindows FormsアプリはCloseを優先する
一般的なWindows Formsアプリでは、メインフォームのClose()を優先するのが分かりやすく安全です。
C#private void exitButton_Click(object sender, EventArgs e)
{
Close();
}
そのうえで、FormClosingイベントに終了確認や保存処理を実装します。
C#private bool hasUnsavedChanges;
private void MainForm_FormClosing(
object sender,
FormClosingEventArgs e)
{
if (!hasUnsavedChanges)
{
return;
}
DialogResult result = MessageBox.Show(
"変更内容を保存せずに終了しますか?",
"終了確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
if (result == DialogResult.No)
{
e.Cancel = true;
}
}
複数フォームを開く構成や、タスクトレイから明示的にアプリ全体を終了する構成では、Application.Exit()を使用します。
C#private void exitApplicationMenuItem_Click(
object sender,
EventArgs e)
{
Application.Exit();
}
Environment.Exit()は、通常の終了処理では対応できない例外的な場面に限定します。
C#private static void StopBecauseOfFatalError()
{
Environment.Exit(1);
}
実装を選ぶ際は、「どれなら確実に終了できるか」だけでなく、「終了前の確認、保存、スレッド停止、リソース解放を正しく実行できるか」という観点が重要です。
まとめ
C#でアプリを終了する方法には、Application.Exit()、Form.Close()、Environment.Exit()がありますが、それぞれ終了対象と強制力が異なります。
Close()は、現在のフォームを通常の手順で閉じる方法です。スタートアップフォームを閉じれば、一般的なWindows Formsアプリではアプリ全体も終了します。サブフォームだけを閉じたい場合にも適しています。
Application.Exit()は、Windows Formsアプリ内の開いているフォームとメッセージループをまとめて終了する方法です。複数フォームを使用するアプリや、タスクトレイ常駐アプリの完全終了に向いています。
Environment.Exit()は、プロセスを直ちに終了します。finallyなどの後処理が実行されないため、通常の終了ボタンでは使用せず、継続不能なエラーや終了コードを伴う強制終了などに限定するべきです。
通常のWindows Formsアプリでは、まずメインフォームのClose()を検討し、アプリ全体を明示的に終了する必要がある場合にApplication.Exit()を使用します。Environment.Exit()は最後の手段として扱うことで、保存漏れやリソース解放漏れの少ない、安全なC#アプリ終了処理を実装できます。

