C# SaveFileDialogの使い方完全ガイド|保存先指定・拡張子設定・上書き確認まで解説

はじめに

C#でデスクトップアプリを作っていると、「ユーザーに保存先を選ばせたい」「保存ファイル名を指定させたい」「txt、csv、jsonなどの拡張子を自動で付けたい」といった場面がよくあります。そこで使うのがSaveFileDialogです。

SaveFileDialogを使うと、Windows標準の「名前を付けて保存」ダイアログを表示し、ユーザーが選択した保存先パスをC#のコードから取得できます。ただし、重要なのは、SaveFileDialogは保存先を選ばせるための部品であり、実際にファイルを書き込む処理はFile.WriteAllTextStreamWriterなどで別途実装する必要がある点です。

この記事では、C# SaveFileDialogの基本的な使い方から、保存先指定、初期ファイル名、拡張子設定、上書き確認、例外処理、WinFormsとWPFそれぞれの完成コードまで、実務で使える形で解説します。

1. C#のSaveFileDialogとは?できることと使う場面

1-1. SaveFileDialogの役割

SaveFileDialogは、ユーザーに保存先とファイル名を指定してもらうためのダイアログです。C#のWindowsアプリで「名前を付けて保存」機能を実装するときに使います。

たとえば、次のような操作をユーザーに行わせたい場合に利用します。

  • テキストファイルの保存先を選ばせる

  • CSVファイルの保存名を指定させる

  • JSONファイルを書き出す場所を選ばせる

  • 画像ファイルの保存先を指定させる

  • 既存ファイルに上書きするか確認させる

WinFormsではSystem.Windows.Forms.SaveFileDialog、WPFではMicrosoft.Win32.SaveFileDialogを使います。どちらも「保存するファイル名をユーザーに指定させる」ための共通ダイアログですが、戻り値や名前空間が異なります。Microsoftの公式ドキュメントでも、WinForms版はSystem.Windows.Forms、WPF版はMicrosoft.Win32のクラスとして定義されています。

1-2. 保存先をユーザーに選ばせる仕組み

SaveFileDialogを表示すると、ユーザーはフォルダ、ファイル名、ファイル種類を選択できます。ユーザーが「保存」ボタンを押すと、選択された保存先のフルパスをFileNameプロパティから取得できます。

ただし、SaveFileDialogを表示しただけではファイルは作成されません。取得したパスに対して、次のような保存処理を書く必要があります。

C#
File.WriteAllText(saveFileDialog.FileName, "保存する内容");

つまり、SaveFileDialogの役割は「保存先パスを決めること」であり、「実際にファイルに書き込むこと」ではありません。

1-3. OpenFileDialogとの違い

OpenFileDialogSaveFileDialogは似ていますが、目的が違います。

OpenFileDialogは、既存ファイルを開くために使います。ユーザーに読み込み対象のファイルを選ばせるときに使います。

一方、SaveFileDialogは、これから保存するファイル名と保存先を指定させるために使います。まだ存在しないファイル名を入力することも想定されています。

違いを整理すると、次のようになります。

ダイアログ主な用途
OpenFileDialog既存ファイルを開くテキストファイルを読み込む
SaveFileDialog保存先とファイル名を指定するCSVファイルを書き出す

1-4. WinFormsとWPFでのSaveFileDialogの違い

WinFormsとWPFでは、使うSaveFileDialogの名前空間とShowDialogの戻り値が異なります。

WinFormsでは次のクラスを使います。

C#
System.Windows.Forms.SaveFileDialog

WinFormsのShowDialog()DialogResultを返します。そのため、ユーザーが保存ボタンを押したかどうかはDialogResult.OKで判定します。MicrosoftのCommonDialog.ShowDialogの例でも、ShowDialog() == DialogResult.OKという形でOK判定が行われています。

C#
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
// 保存処理
}

WPFでは次のクラスを使います。

C#
Microsoft.Win32.SaveFileDialog

WPFのShowDialog()bool?を返します。ユーザーが保存を確定した場合はtrueになります。MicrosoftのWPF版SaveFileDialogの例でも、Nullable<bool> result = dlg.ShowDialog();として結果を受け取り、trueの場合にFileNameを取得しています。

C#
if (saveFileDialog.ShowDialog() == true)
{
// 保存処理
}

1-5. SaveFileDialogがよく使われる実装例

SaveFileDialogは、次のような機能でよく使われます。

  • メモ帳アプリの「名前を付けて保存」

  • データ一覧のCSV出力

  • 設定情報のJSONエクスポート

  • ログファイルの保存

  • 画像編集アプリの画像保存

  • 帳票やレポートの出力

  • XML設定ファイルの書き出し

たとえば、業務アプリでは「検索結果をCSV出力する」機能がよくあります。この場合、SaveFileDialogで保存先を選択し、選ばれたパスに対してCSV文字列を書き込みます。

2. SaveFileDialogを使う前の準備

2-1. 必要な名前空間

WinFormsでSaveFileDialogを使う場合は、次の名前空間を使用します。

C#
using System.Windows.Forms;

ファイル保存処理も行う場合は、あわせて次の名前空間もよく使います。

C#
using System.IO;
using System.Text;

WPFで使う場合は、次の名前空間を使用します。

C#
using Microsoft.Win32;

こちらもファイル保存にはSystem.IOSystem.Textを使います。

C#
using System.IO;
using System.Text;
using Microsoft.Win32;

2-2. WinFormsで使う場合の準備

WinFormsアプリでは、フォーム上のボタンイベントなどにSaveFileDialogの処理を書きます。

たとえば、保存ボタンのクリックイベントで使う場合は次のようになります。

C#
private void buttonSave_Click(object sender, EventArgs e)
{
using SaveFileDialog dialog = new SaveFileDialog();

dialog.Title = "ファイルを保存";
dialog.Filter = "テキストファイル (*.txt)|*.txt";
dialog.DefaultExt = "txt";
dialog.AddExtension = true;

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

File.WriteAllText(dialog.FileName, "保存する内容", Encoding.UTF8);
}

WinFormsではSaveFileDialogIDisposableを実装しているため、基本的にはusingで囲んで使うと安全です。

2-3. WPFで使う場合の準備

WPFでは、Microsoft.Win32.SaveFileDialogを使います。WinForms版とクラス名は同じですが、名前空間が違う点に注意してください。

C#
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new Microsoft.Win32.SaveFileDialog
{
Title = "ファイルを保存",
FileName = "sample",
DefaultExt = ".txt",
Filter = "テキストファイル (*.txt)|*.txt"
};

if (dialog.ShowDialog() != true)
{
return;
}

File.WriteAllText(dialog.FileName, "保存する内容", Encoding.UTF8);
}

WPFでは、ShowDialog()の戻り値をDialogResult.OKではなくtrueで判定します。この違いは、WPFでSaveFileDialogがうまく動かない原因になりやすいポイントです。

2-4. .NET Frameworkと.NETの違い

SaveFileDialogは、.NET FrameworkのWinForms/WPFアプリでも、現在の.NETのWindowsデスクトップアプリでも利用できます。ただし、プロジェクトの種類によって参照するUIフレームワークが異なります。

WinFormsの場合は、System.Windows.Formsを使用します。

WPFの場合は、Microsoft.Win32を使用します。

.NET 5以降や.NET 6以降のプロジェクトでは、Windowsデスクトップ向けプロジェクトとして作成されている必要があります。コンソールアプリだけでSaveFileDialogを使う場合は、Windows FormsやWPFの参照設定が必要になることがあります。

2-5. サンプルコードを動かす前提条件

この記事のサンプルコードは、次の前提で解説します。

  • C#のWindowsデスクトップアプリを対象にする

  • WinFormsまたはWPFで実行する

  • 保存処理にはFile.WriteAllTextStreamWriterを使う

  • 文字コードは基本的にUTF-8を使う

  • ファイルの保存先はユーザーに選ばせる

コンソールアプリやASP.NETなど、画面を持たないアプリではSaveFileDialogは通常使いません。Webアプリでファイルをダウンロードさせたい場合は、HTTPレスポンスとしてファイルを返す実装になります。

3. C# SaveFileDialogの基本的な使い方

3-1. 最小構成のサンプルコード

まずは、WinFormsでSaveFileDialogを表示し、選択された保存先にテキストを保存する最小構成です。

C#
using System.IO;
using System.Text;
using System.Windows.Forms;

private void buttonSave_Click(object sender, EventArgs e)
{
using SaveFileDialog dialog = new SaveFileDialog();

if (dialog.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(dialog.FileName, "Hello, SaveFileDialog!", Encoding.UTF8);
}
}

このコードでは、ShowDialog()で保存ダイアログを表示し、ユーザーが保存を確定した場合だけFile.WriteAllTextでファイルを書き込んでいます。

3-2. ShowDialogで保存ダイアログを表示する

SaveFileDialogは、インスタンスを作成しただけでは画面に表示されません。保存ダイアログを表示するにはShowDialog()を呼び出します。

C#
using SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();

ただし、この書き方だけではユーザーが保存を押したのか、キャンセルしたのかを判定していません。実務では必ず戻り値を確認します。

3-3. DialogResultでOK・キャンセルを判定する

WinFormsでは、ShowDialog()の戻り値をDialogResult.OKで判定します。

C#
if (dialog.ShowDialog() == DialogResult.OK)
{
// 保存ボタンが押された場合
}

キャンセルされた場合は保存処理を実行しないようにします。

C#
if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

このように先にキャンセルを判定してreturnする書き方にすると、保存処理が下にまとまり、読みやすいコードになります。

3-4. FileNameで保存先パスを取得する

ユーザーが保存先を指定して保存ボタンを押すと、FileNameプロパティから保存先のフルパスを取得できます。

C#
string path = dialog.FileName;

たとえば、ユーザーがデスクトップにsample.txtとして保存する場合、FileNameには次のような文字列が入ります。

C:\Users\ユーザー名\Desktop\sample.txt

WPF版の公式ドキュメントでも、ShowDialog()の結果が有効な場合にdlg.FileNameからファイル名を取得する例が示されています。

3-5. キャンセル時に処理しない書き方

よくあるミスは、ユーザーがキャンセルしたにもかかわらず、dialog.FileNameを使って保存処理を続けてしまうことです。

悪い例は次の通りです。

C#
using SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();

File.WriteAllText(dialog.FileName, "保存する内容");

このコードでは、キャンセル時にもFile.WriteAllTextが実行される可能性があります。

安全な書き方は次の通りです。

C#
using SaveFileDialog dialog = new SaveFileDialog();

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

File.WriteAllText(dialog.FileName, "保存する内容", Encoding.UTF8);

WPFの場合は次のように書きます。

C#
var dialog = new Microsoft.Win32.SaveFileDialog();

if (dialog.ShowDialog() != true)
{
return;
}

File.WriteAllText(dialog.FileName, "保存する内容", Encoding.UTF8);

4. 保存先・ファイル名を指定する方法

4-1. InitialDirectoryで初期フォルダを指定する

InitialDirectoryを使うと、保存ダイアログを開いたときに最初に表示するフォルダを指定できます。

C#
dialog.InitialDirectory = @"C:\Temp";

たとえば、ドキュメントフォルダを初期表示したい場合は、Environment.GetFolderPathを使うと安全です。

C#
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

ユーザー環境によってユーザー名やフォルダ構成は異なるため、C:\Users\...のようなパスを直接書くより、Environment.SpecialFolderを使う方が実務向きです。

4-2. FileNameで初期ファイル名を設定する

FileNameは、保存後のパス取得だけでなく、ダイアログに初期表示するファイル名の設定にも使えます。

C#
dialog.FileName = "export.csv";

たとえば、日付を含めたファイル名にしたい場合は次のように書けます。

C#
dialog.FileName = $"売上データ_{DateTime.Now:yyyyMMdd}.csv";

ユーザーにとって分かりやすい初期ファイル名を設定しておくと、保存時の手間を減らせます。

4-3. Titleでダイアログのタイトルを変更する

Titleを設定すると、保存ダイアログのタイトルバーに表示される文字列を変更できます。

C#
dialog.Title = "CSVファイルを保存";

処理内容に合わせてタイトルを変えると、ユーザーが何を保存しようとしているのか分かりやすくなります。

C#
dialog.Title = "ログファイルの保存先を選択してください";

4-4. RestoreDirectoryで現在のディレクトリを維持する

RestoreDirectorytrueにすると、ダイアログを閉じたあとに現在のディレクトリを元に戻す動作を指定できます。

C#
dialog.RestoreDirectory = true;

アプリケーションによっては、ファイルダイアログを開いたことでカレントディレクトリが変わると困るケースがあります。特に相対パスを使っている古いコードや、外部ファイルを相対パスで参照しているアプリでは、RestoreDirectory = trueを設定しておくと安全です。

4-5. デスクトップやドキュメントを初期表示する方法

デスクトップを初期表示する場合は、次のように書きます。

C#
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

ドキュメントフォルダを初期表示する場合は、次のように書きます。

C#
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

ピクチャフォルダを初期表示する場合は、次のように書きます。

C#
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

保存するファイルの種類に合わせて初期フォルダを変えると、ユーザーが保存先を探す手間を減らせます。

5. 拡張子とファイル種類を設定する方法

5-1. Filterで保存できる拡張子を指定する

Filterを設定すると、保存ダイアログの「ファイルの種類」に表示される選択肢を指定できます。

C#
dialog.Filter = "テキストファイル (*.txt)|*.txt";

Filterは、表示名と実際の拡張子パターンを|で区切って書きます。

表示名|拡張子パターン

たとえば、CSVファイルなら次のように書きます。

C#
dialog.Filter = "CSVファイル (*.csv)|*.csv";

FilterSaveFileDialogで特に重要なプロパティです。Microsoftのドキュメントでも、Filterはダイアログの「Save as file type」や「Files of type」に表示される選択肢を決めるプロパティとして説明されています。

5-2. 複数の拡張子を選択肢にする書き方

複数のファイル種類を選ばせたい場合は、表示名と拡張子パターンのセットを続けて書きます。

C#
dialog.Filter =
"テキストファイル (*.txt)|*.txt|" +
"CSVファイル (*.csv)|*.csv|" +
"すべてのファイル (*.*)|*.*";

ファイル種類ごとに複数の拡張子をまとめたい場合は、セミコロンで区切ります。

C#
dialog.Filter = "画像ファイル (*.png;*.jpg;*.jpeg)|*.png;*.jpg;*.jpeg";

|の数や順序が間違っているとエラーの原因になります。必ず「表示名|パターン」のペアで書きます。

5-3. FilterIndexで初期選択のファイル種類を指定する

FilterIndexを設定すると、複数のFilterのうち、最初に選択されるファイル種類を指定できます。

C#
dialog.Filter =
"テキストファイル (*.txt)|*.txt|" +
"CSVファイル (*.csv)|*.csv|" +
"JSONファイル (*.json)|*.json";

dialog.FilterIndex = 2;

この例では、2番目の「CSVファイル」が初期選択されます。

注意点として、FilterIndexは0始まりではなく1始まりです。1番目を選びたい場合は1、2番目を選びたい場合は2を指定します。

5-4. DefaultExtで既定の拡張子を設定する

DefaultExtを設定すると、ユーザーが拡張子を入力しなかった場合に使う既定の拡張子を指定できます。

WinFormsでは次のように書くことが多いです。

C#
dialog.DefaultExt = "txt";

WPFでは、Microsoftのサンプルのようにドット付きで指定する例もあります。

C#
dialog.DefaultExt = ".txt";

拡張子の自動付与を期待する場合は、DefaultExtだけでなくAddExtensionも設定しておくと分かりやすくなります。

5-5. AddExtensionで拡張子を自動付与する

AddExtensiontrueにすると、ユーザーが拡張子を省略した場合に、既定の拡張子が自動で付与されます。Microsoftのドキュメントでも、AddExtensionはユーザーが拡張子を省略した場合に自動的に追加するかどうかを示すプロパティとして説明されています。

C#
dialog.DefaultExt = "txt";
dialog.AddExtension = true;

たとえば、ユーザーがsampleと入力した場合に、sample.txtとして保存されることを期待できます。

拡張子が付かないトラブルを避けるため、FilterDefaultExtAddExtensionはセットで設定するのがおすすめです。

C#
dialog.Filter = "テキストファイル (*.txt)|*.txt";
dialog.DefaultExt = "txt";
dialog.AddExtension = true;

5-6. txt・csv・json・xml・画像ファイルの設定例

よく使うFilter設定例を紹介します。

テキストファイルの場合です。

C#
dialog.Filter = "テキストファイル (*.txt)|*.txt";
dialog.DefaultExt = "txt";
dialog.AddExtension = true;

CSVファイルの場合です。

C#
dialog.Filter = "CSVファイル (*.csv)|*.csv";
dialog.DefaultExt = "csv";
dialog.AddExtension = true;

JSONファイルの場合です。

C#
dialog.Filter = "JSONファイル (*.json)|*.json";
dialog.DefaultExt = "json";
dialog.AddExtension = true;

XMLファイルの場合です。

C#
dialog.Filter = "XMLファイル (*.xml)|*.xml";
dialog.DefaultExt = "xml";
dialog.AddExtension = true;

画像ファイルの場合です。

C#
dialog.Filter =
"PNG画像 (*.png)|*.png|" +
"JPEG画像 (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"ビットマップ画像 (*.bmp)|*.bmp";

dialog.DefaultExt = "png";
dialog.AddExtension = true;

複数形式をサポートする場合は、ユーザーが選んだFilterIndexに応じて保存形式を切り替える実装にします。

6. 上書き確認や存在チェックを制御する方法

6-1. OverwritePromptで上書き確認を表示する

OverwritePrompttrueにすると、ユーザーが既存ファイル名を指定した場合に上書き確認を表示できます。Microsoftの公式ドキュメントでも、OverwritePromptは既に存在するファイル名を指定した場合に警告を表示するかどうかを示すプロパティとして説明されています。

C#
dialog.OverwritePrompt = true;

通常は、誤って既存ファイルを上書きしないようにtrueにしておくのがおすすめです。

C#
using SaveFileDialog dialog = new SaveFileDialog
{
Filter = "テキストファイル (*.txt)|*.txt",
DefaultExt = "txt",
AddExtension = true,
OverwritePrompt = true
};

6-2. CreatePromptで存在しないファイルの作成確認を表示する

CreatePrompttrueにすると、存在しないファイル名が指定された場合に、作成確認を表示できます。

C#
dialog.CreatePrompt = true;

ただし、保存ダイアログでは新しいファイル名を指定することが多いため、実務ではCreatePromptfalseのままにすることも多いです。毎回作成確認が出ると、ユーザー操作が煩雑になるためです。

6-3. CheckPathExistsで存在するフォルダだけ許可する

CheckPathExiststrueにすると、存在しないパスが指定された場合に警告を表示できます。

C#
dialog.CheckPathExists = true;

存在しないフォルダに保存しようとすると、保存処理でDirectoryNotFoundExceptionなどの例外が発生する可能性があります。ユーザー入力によるパス指定を許可する場合は、CheckPathExistsを有効にしておくと安全です。

6-4. ValidateNamesで不正なファイル名を防ぐ

ValidateNamestrueにすると、無効なWin32ファイル名を受け付けないようにできます。WPF版のドキュメントでも、ValidateNamesは有効なWin32ファイル名のみを受け入れるかどうかを示すプロパティとして説明されています。

C#
dialog.ValidateNames = true;

Windowsでは、ファイル名に使えない文字があります。

\ / : * ? " < > |

通常はダイアログ側で防げますが、コード側でも必要に応じてチェックするとさらに安全です。

6-5. 同名ファイルがある場合の安全な処理

OverwritePrompt = trueにしておけば、基本的にはダイアログ側で上書き確認が行われます。ただし、保存処理を実行する直前にファイル状態が変わる可能性もあります。

より安全にするなら、保存直前にFile.Existsで確認する方法もあります。

C#
if (File.Exists(dialog.FileName))
{
DialogResult result = MessageBox.Show(
"同名のファイルが存在します。上書きしますか?",
"上書き確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);

if (result != DialogResult.Yes)
{
return;
}
}

ただし、OverwritePromptと独自確認の両方を入れると確認が二重になる場合があります。基本はOverwritePromptを使い、特別な要件がある場合だけ独自確認を追加しましょう。

7. SaveFileDialogで実際にファイルを保存する実装例

7-1. テキストファイルを保存するサンプル

テキストファイルを保存する基本例です。

C#
using System.IO;
using System.Text;
using System.Windows.Forms;

private void SaveText()
{
using SaveFileDialog dialog = new SaveFileDialog
{
Title = "テキストファイルを保存",
Filter = "テキストファイル (*.txt)|*.txt",
DefaultExt = "txt",
AddExtension = true,
OverwritePrompt = true
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

string text = "これは保存するテキストです。";
File.WriteAllText(dialog.FileName, text, Encoding.UTF8);

MessageBox.Show("保存しました。", "保存完了",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}

File.WriteAllTextは、指定した文字列をファイルに書き込み、ファイルを閉じます。既存ファイルがある場合は内容が上書きされます。Microsoftのドキュメントでも、WriteAllTextはファイルを作成して内容を書き込み、対象ファイルが存在する場合は切り詰めて上書きすると説明されています。

7-2. CSVファイルを保存するサンプル

CSVファイルを保存する例です。

C#
using System.IO;
using System.Text;
using System.Windows.Forms;

private void SaveCsv()
{
using SaveFileDialog dialog = new SaveFileDialog
{
Title = "CSVファイルを保存",
Filter = "CSVファイル (*.csv)|*.csv",
DefaultExt = "csv",
AddExtension = true,
OverwritePrompt = true,
FileName = $"export_{DateTime.Now:yyyyMMdd}.csv"
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

var lines = new[]
{
"ID,名前,金額",
"1,田中,1000",
"2,佐藤,2000",
"3,鈴木,3000"
};

File.WriteAllLines(dialog.FileName, lines, new UTF8Encoding(true));

MessageBox.Show("CSVファイルを保存しました。");
}

Excelで開くCSVの場合、環境によってはBOM付きUTF-8の方が文字化けしにくいことがあります。そのため、上記ではnew UTF8Encoding(true)を指定しています。

7-3. JSONファイルを保存するサンプル

JSONファイルを保存する例です。

C#
using System.IO;
using System.Text;
using System.Text.Json;
using System.Windows.Forms;

private void SaveJson()
{
using SaveFileDialog dialog = new SaveFileDialog
{
Title = "JSONファイルを保存",
Filter = "JSONファイル (*.json)|*.json",
DefaultExt = "json",
AddExtension = true,
OverwritePrompt = true,
FileName = "settings.json"
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

var data = new
{
AppName = "SampleApp",
Version = "1.0.0",
ExportedAt = DateTime.Now
};

string json = JsonSerializer.Serialize(data, new JsonSerializerOptions
{
WriteIndented = true
});

File.WriteAllText(dialog.FileName, json, Encoding.UTF8);

MessageBox.Show("JSONファイルを保存しました。");
}

System.Text.Jsonは、C#でオブジェクトをJSONにシリアライズする標準的なAPIです。Microsoftのドキュメントでも、System.Text.Json名前空間はJSONのシリアライズとデシリアライズの機能を提供すると説明されています。

7-4. 画像ファイルを保存するサンプル

WinFormsでPictureBoxに表示されている画像をPNGとして保存する例です。

C#
using System.Drawing.Imaging;
using System.Windows.Forms;

private void SaveImage()
{
if (pictureBox1.Image == null)
{
MessageBox.Show("保存する画像がありません。");
return;
}

using SaveFileDialog dialog = new SaveFileDialog
{
Title = "画像を保存",
Filter = "PNG画像 (*.png)|*.png|JPEG画像 (*.jpg)|*.jpg|ビットマップ画像 (*.bmp)|*.bmp",
FilterIndex = 1,
DefaultExt = "png",
AddExtension = true,
OverwritePrompt = true,
FileName = "image.png"
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

ImageFormat format = dialog.FilterIndex switch
{
1 => ImageFormat.Png,
2 => ImageFormat.Jpeg,
3 => ImageFormat.Bmp,
_ => ImageFormat.Png
};

pictureBox1.Image.Save(dialog.FileName, format);

MessageBox.Show("画像を保存しました。");
}

画像保存では、選択された拡張子と実際の保存形式を一致させることが重要です。FilterIndexを使って、PNG、JPEG、BMPを切り替えています。

7-5. StreamWriterを使った保存処理

StreamWriterを使うと、行単位で書き込みたい場合や、大きめのテキストを順番に出力したい場合に便利です。Microsoftのドキュメントでも、テキストファイルへの書き込みにはStreamWriterFileクラスがよく使われると説明されています。

C#
using System.IO;
using System.Text;
using System.Windows.Forms;

private void SaveWithStreamWriter()
{
using SaveFileDialog dialog = new SaveFileDialog
{
Filter = "テキストファイル (*.txt)|*.txt",
DefaultExt = "txt",
AddExtension = true,
OverwritePrompt = true
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

using StreamWriter writer = new StreamWriter(dialog.FileName, false, Encoding.UTF8);
writer.WriteLine("1行目");
writer.WriteLine("2行目");
writer.WriteLine("3行目");
}

StreamWriterの第2引数にfalseを指定すると上書き、trueを指定すると追記になります。

7-6. File.WriteAllTextを使った保存処理

少量のテキストを一度に保存する場合は、File.WriteAllTextが簡単です。

C#
File.WriteAllText(dialog.FileName, text, Encoding.UTF8);

複数行を配列で保存するなら、File.WriteAllLinesも便利です。

C#
string[] lines =
{
"1行目",
"2行目",
"3行目"
};

File.WriteAllLines(dialog.FileName, lines, Encoding.UTF8);

設定ファイル、ログの書き出し、メモ帳の保存などでは、File.WriteAllTextまたはStreamWriterを使うことが多いです。

8. 例外処理とエラー対策

8-1. 保存時に発生しやすい例外

SaveFileDialogで保存先を選んでも、実際の保存時には例外が発生する可能性があります。代表的な例外は次の通りです。

例外主な原因
UnauthorizedAccessExceptionアクセス権がない、読み取り専用、フォルダを指定した
IOExceptionファイル使用中、I/Oエラー
DirectoryNotFoundException保存先フォルダが存在しない
PathTooLongExceptionパスが長すぎる
NotSupportedExceptionパス形式が不正
ArgumentExceptionパス文字列が不正

File.WriteAllTextの公式ドキュメントにも、UnauthorizedAccessExceptionIOExceptionPathTooLongExceptionDirectoryNotFoundExceptionなどの例外が記載されています。

8-2. UnauthorizedAccessExceptionへの対応

UnauthorizedAccessExceptionは、保存先に書き込み権限がない場合や、フォルダパスをファイルとして指定した場合などに発生します。

対策としては、次のようなメッセージを表示します。

C#
catch (UnauthorizedAccessException)
{
MessageBox.Show(
"指定された場所に保存する権限がありません。別の保存先を選択してください。",
"アクセスエラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}

Program Files配下やWindowsシステムフォルダなど、一般ユーザーが書き込めない場所では発生しやすいので注意しましょう。

8-3. IOExceptionへの対応

IOExceptionは、ファイルが他のアプリで使用中の場合や、ディスクI/Oに問題がある場合などに発生します。

C#
catch (IOException ex)
{
MessageBox.Show(
$"ファイルの保存中にエラーが発生しました。\n{ex.Message}",
"I/Oエラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}

たとえば、保存先のCSVファイルをExcelで開いたままにしていると、保存に失敗することがあります。

8-4. パスが長すぎる場合の対策

PathTooLongExceptionは、保存先パスが長すぎる場合に発生します。深い階層のフォルダに長いファイル名で保存しようとすると発生する可能性があります。

対策としては、ユーザーに短いパスを選び直してもらいます。

C#
catch (PathTooLongException)
{
MessageBox.Show(
"保存先のパスが長すぎます。別のフォルダまたは短いファイル名を指定してください。",
"パスが長すぎます",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
}

8-5. ファイルが使用中の場合の対策

ファイルが他のアプリで開かれている場合、保存に失敗することがあります。たとえば、CSVファイルをExcelで開いたまま上書き保存しようとするケースです。

ユーザー向けには、次のような案内が分かりやすいです。

ファイルが他のアプリで使用中の可能性があります。
ファイルを閉じてから、もう一度保存してください。

コードではIOExceptionとして扱うことが多いです。

8-6. try-catchを使った安全な保存コード

実務では、保存処理をtry-catchで囲みます。

C#
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

private void SaveSafely()
{
using SaveFileDialog dialog = new SaveFileDialog
{
Title = "テキストファイルを保存",
Filter = "テキストファイル (*.txt)|*.txt",
DefaultExt = "txt",
AddExtension = true,
OverwritePrompt = true,
CheckPathExists = true,
ValidateNames = true
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

try
{
File.WriteAllText(dialog.FileName, "保存する内容", Encoding.UTF8);

MessageBox.Show(
"ファイルを保存しました。",
"保存完了",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (UnauthorizedAccessException)
{
MessageBox.Show(
"指定された場所に保存する権限がありません。",
"アクセスエラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
catch (IOException ex)
{
MessageBox.Show(
$"ファイルの保存中にエラーが発生しました。\n{ex.Message}",
"保存エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show(
$"予期しないエラーが発生しました。\n{ex.Message}",
"エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}

9. SaveFileDialogでよくあるトラブルと解決策

9-1. 拡張子が付かない原因

拡張子が付かない主な原因は、DefaultExtAddExtensionが設定されていないことです。

次のように設定しましょう。

C#
dialog.Filter = "テキストファイル (*.txt)|*.txt";
dialog.DefaultExt = "txt";
dialog.AddExtension = true;

また、ユーザーがファイル名に別の拡張子を入力した場合は、その入力が優先されることがあります。拡張子を完全に固定したい場合は、保存前にコード側で拡張子を確認する必要があります。

9-2. Filterの書き方でエラーになる原因

Filterは必ず「表示名|パターン」のペアで書きます。

正しい例です。

C#
dialog.Filter = "テキストファイル (*.txt)|*.txt";

複数指定の正しい例です。

C#
dialog.Filter = "テキストファイル (*.txt)|*.txt|CSVファイル (*.csv)|*.csv";

間違った例です。

C#
dialog.Filter = "テキストファイル (*.txt)";

|の数が合わない、表示名とパターンがペアになっていない、拡張子パターンを書いていない、といったミスに注意してください。

9-3. キャンセルしても処理が進む原因

キャンセルしても処理が進む原因は、ShowDialog()の戻り値を判定していないことです。

悪い例です。

C#
dialog.ShowDialog();
File.WriteAllText(dialog.FileName, text);

正しい例です。

C#
if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

File.WriteAllText(dialog.FileName, text, Encoding.UTF8);

WPFでは次のように書きます。

C#
if (dialog.ShowDialog() != true)
{
return;
}

9-4. 保存先パスが取得できない原因

保存先パスは、ユーザーが保存を確定したあとにFileNameから取得します。

C#
string path = dialog.FileName;

ただし、キャンセルされた場合は有効なパスが入っていない可能性があります。必ずShowDialog()の結果を確認してからFileNameを使いましょう。

9-5. WPFでDialogResultの判定がうまくいかない原因

WPFのMicrosoft.Win32.SaveFileDialogでは、WinFormsのようにDialogResult.OKで判定しません。

間違った例です。

C#
if (dialog.ShowDialog() == DialogResult.OK)
{
// WPFではこの書き方はしない
}

正しい例です。

C#
if (dialog.ShowDialog() == true)
{
File.WriteAllText(dialog.FileName, text, Encoding.UTF8);
}

WinFormsとWPFで戻り値の型が違うため、サンプルコードを流用するときは注意が必要です。

9-6. 日本語ファイル名や特殊文字で失敗する場合

日本語ファイル名自体は通常問題ありません。ただし、次のような文字はWindowsのファイル名に使えません。

\ / : * ? " < > |

また、保存するテキストの文字化けを防ぐには、文字コードを明示することが重要です。

C#
File.WriteAllText(dialog.FileName, text, Encoding.UTF8);

CSVをExcelで開く前提なら、BOM付きUTF-8を使うことも検討します。

C#
File.WriteAllText(dialog.FileName, text, new UTF8Encoding(true));

10. 実務で使いやすいSaveFileDialogの完成コード

10-1. WinForms向けの完成サンプル

WinFormsでテキストファイルを安全に保存する完成コードです。

C#
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

private void buttonSave_Click(object sender, EventArgs e)
{
using SaveFileDialog dialog = new SaveFileDialog
{
Title = "テキストファイルを保存",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
FileName = $"memo_{DateTime.Now:yyyyMMdd}.txt",
Filter = "テキストファイル (*.txt)|*.txt|すべてのファイル (*.*)|*.*",
FilterIndex = 1,
DefaultExt = "txt",
AddExtension = true,
OverwritePrompt = true,
CheckPathExists = true,
ValidateNames = true,
RestoreDirectory = true
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}

try
{
string text = textBox1.Text;

File.WriteAllText(dialog.FileName, text, Encoding.UTF8);

MessageBox.Show(
"ファイルを保存しました。",
"保存完了",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (UnauthorizedAccessException)
{
MessageBox.Show(
"指定された場所に保存する権限がありません。別の保存先を選択してください。",
"アクセスエラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
catch (IOException ex)
{
MessageBox.Show(
$"ファイルの保存中にエラーが発生しました。\n{ex.Message}",
"保存エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show(
$"予期しないエラーが発生しました。\n{ex.Message}",
"エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}

このコードでは、初期フォルダ、初期ファイル名、拡張子、自動付与、上書き確認、パス存在チェック、例外処理まで含めています。

10-2. WPF向けの完成サンプル

WPFでテキストファイルを保存する完成コードです。

C#
using System;
using System.IO;
using System.Text;
using System.Windows;
using Microsoft.Win32;

private void SaveButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new SaveFileDialog
{
Title = "テキストファイルを保存",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
FileName = $"memo_{DateTime.Now:yyyyMMdd}.txt",
Filter = "テキストファイル (*.txt)|*.txt|すべてのファイル (*.*)|*.*",
FilterIndex = 1,
DefaultExt = ".txt",
AddExtension = true,
OverwritePrompt = true,
CheckPathExists = true,
ValidateNames = true
};

if (dialog.ShowDialog(this) != true)
{
return;
}

try
{
string text = TextBoxMemo.Text;

File.WriteAllText(dialog.FileName, text, Encoding.UTF8);

MessageBox.Show(
"ファイルを保存しました。",
"保存完了",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (UnauthorizedAccessException)
{
MessageBox.Show(
"指定された場所に保存する権限がありません。別の保存先を選択してください。",
"アクセスエラー",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
catch (IOException ex)
{
MessageBox.Show(
$"ファイルの保存中にエラーが発生しました。\n{ex.Message}",
"保存エラー",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show(
$"予期しないエラーが発生しました。\n{ex.Message}",
"エラー",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}

WPFでは、ShowDialog(this)のようにオーナーウィンドウを指定すると、ダイアログの前後関係が安定します。

10-3. テキスト・CSV保存に使い回せる共通メソッド

保存ダイアログを何度も書く場合は、共通メソッドにすると便利です。

WinForms向けの例です。

C#
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

private bool SaveTextFile(
string content,
string title,
string defaultFileName,
string filter,
string defaultExt,
Encoding encoding)
{
using SaveFileDialog dialog = new SaveFileDialog
{
Title = title,
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
FileName = defaultFileName,
Filter = filter,
DefaultExt = defaultExt,
AddExtension = true,
OverwritePrompt = true,
CheckPathExists = true,
ValidateNames = true,
RestoreDirectory = true
};

if (dialog.ShowDialog() != DialogResult.OK)
{
return false;
}

try
{
File.WriteAllText(dialog.FileName, content, encoding);
return true;
}
catch (Exception ex)
{
MessageBox.Show(
$"保存に失敗しました。\n{ex.Message}",
"保存エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);

return false;
}
}

使う側は次のようにシンプルになります。

C#
SaveTextFile(
content: textBox1.Text,
title: "テキストファイルを保存",
defaultFileName: "memo.txt",
filter: "テキストファイル (*.txt)|*.txt",
defaultExt: "txt",
encoding: Encoding.UTF8);

CSV保存にも使えます。

C#
SaveTextFile(
content: csvText,
title: "CSVファイルを保存",
defaultFileName: $"export_{DateTime.Now:yyyyMMdd}.csv",
filter: "CSVファイル (*.csv)|*.csv",
defaultExt: "csv",
encoding: new UTF8Encoding(true));

10-4. ユーザー操作を考慮した安全な実装

実務では、単に保存できるだけでなく、ユーザーが迷わないことも重要です。

次の点を意識すると、使いやすい保存機能になります。

  • 初期ファイル名を分かりやすくする

  • 保存形式に合ったFilterを用意する

  • OverwritePromptを有効にする

  • キャンセル時は何もしない

  • 保存成功時にメッセージを表示する

  • 保存失敗時に原因が分かるメッセージを表示する

  • 文字コードを明示する

  • 例外処理を入れる

特に、キャンセル時にエラーメッセージを出す必要はありません。ユーザーが意図してキャンセルしただけなので、静かに処理を終了する方が自然です。

10-5. コピペで使うときの注意点

サンプルコードをコピーして使うときは、次の点を確認してください。

WinFormsとWPFのSaveFileDialogを混同しないようにしましょう。

WinFormsの場合です。

C#
using System.Windows.Forms;

WPFの場合です。

C#
using Microsoft.Win32;

また、MessageBoxもWinFormsとWPFで名前空間や引数が異なります。WinFormsのコードをWPFに貼り付けると、そのままではコンパイルできない場合があります。

さらに、画面上のコントロール名も自分のプロジェクトに合わせて変更してください。

C#
textBox1.Text      // WinFormsの例
TextBoxMemo.Text // WPFの例

11. SaveFileDialogを使う際のベストプラクティス

11-1. ユーザーに分かりやすい初期ファイル名を設定する

FileNameには、保存内容が分かる名前を設定しましょう。

C#
dialog.FileName = $"売上データ_{DateTime.Now:yyyyMMdd}.csv";

単にdata.csvexport.txtにするより、日付や内容を含めた方が後から見つけやすくなります。

11-2. 保存形式に合ったFilterを用意する

保存形式が決まっている場合は、Filterで適切な拡張子だけを表示します。

C#
dialog.Filter = "CSVファイル (*.csv)|*.csv";

複数形式をサポートする場合は、選択肢を分かりやすく並べます。

C#
dialog.Filter =
"CSVファイル (*.csv)|*.csv|" +
"テキストファイル (*.txt)|*.txt|" +
"すべてのファイル (*.*)|*.*";

11-3. 上書き確認を有効にする

既存ファイルの上書きは、ユーザーにとって重要な操作です。基本的にはOverwritePromptを有効にしましょう。

C#
dialog.OverwritePrompt = true;

誤って重要なファイルを上書きするリスクを減らせます。

11-4. 例外処理を必ず入れる

保存先を選択できても、実際に保存できるとは限りません。アクセス権、ファイル使用中、パス不正、ディスクエラーなどに備えて、保存処理はtry-catchで囲みます。

C#
try
{
File.WriteAllText(dialog.FileName, text, Encoding.UTF8);
}
catch (Exception ex)
{
MessageBox.Show($"保存に失敗しました。\n{ex.Message}");
}

ただし、実務ではExceptionだけでまとめるのではなく、UnauthorizedAccessExceptionIOExceptionを個別に処理すると、ユーザーに分かりやすいメッセージを出せます。

11-5. 保存完了メッセージを表示する

保存が成功したら、ユーザーに完了を知らせると親切です。

C#
MessageBox.Show("ファイルを保存しました。");

ただし、頻繁に保存するアプリでは、毎回メッセージボックスを出すと操作の邪魔になることがあります。その場合は、ステータスバーや画面下部のメッセージ表示にするのも良い方法です。

11-6. パスや拡張子をコード側でも確認する

FilterDefaultExtを設定していても、ユーザーが手動で別の拡張子を入力する場合があります。拡張子を固定したい場合は、保存直前にコード側でチェックします。

C#
string path = dialog.FileName;

if (Path.GetExtension(path).ToLowerInvariant() != ".csv")
{
path = Path.ChangeExtension(path, ".csv");
}

拡張子と保存内容が一致していないと、後でファイルを開くときに問題になることがあります。

12. C# SaveFileDialogに関するよくある質問

12-1. SaveFileDialogでフォルダだけ選択できる?

SaveFileDialogは、ファイル名と保存先を指定するためのダイアログです。フォルダだけを選択する用途には向いていません。

フォルダだけを選択したい場合は、WinFormsならFolderBrowserDialogを使います。

C#
using FolderBrowserDialog dialog = new FolderBrowserDialog();

if (dialog.ShowDialog() == DialogResult.OK)
{
string folderPath = dialog.SelectedPath;
}

保存ファイル名までユーザーに指定させたいならSaveFileDialog、フォルダだけ選ばせたいならFolderBrowserDialogと使い分けます。

12-2. 保存先のフルパスを取得するには?

保存先のフルパスは、FileNameプロパティから取得します。

WinFormsの場合です。

C#
if (dialog.ShowDialog() == DialogResult.OK)
{
string path = dialog.FileName;
}

WPFの場合です。

C#
if (dialog.ShowDialog() == true)
{
string path = dialog.FileName;
}

FileNameには、フォルダ名とファイル名を含むフルパスが入ります。

12-3. 拡張子を固定して変更できないようにできる?

FilterDefaultExtAddExtensionを設定すると、通常の操作では指定拡張子で保存しやすくなります。

C#
dialog.Filter = "CSVファイル (*.csv)|*.csv";
dialog.DefaultExt = "csv";
dialog.AddExtension = true;

ただし、ユーザーがファイル名欄に手動で別の拡張子を入力する可能性はあります。完全に固定したい場合は、保存直前にPath.ChangeExtensionで拡張子を強制的に変更します。

C#
string path = Path.ChangeExtension(dialog.FileName, ".csv");
File.WriteAllText(path, csvText, Encoding.UTF8);

12-4. 既定の保存先をデスクトップにできる?

できます。InitialDirectoryにデスクトップのパスを設定します。

C#
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

ドキュメントにしたい場合は次のようにします。

C#
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

12-5. 上書き確認を表示しない方法は?

OverwritePromptfalseにすると、上書き確認を表示しないようにできます。

C#
dialog.OverwritePrompt = false;

ただし、既存ファイルを確認なしで上書きするのは危険です。ユーザーが意図せず重要なファイルを失う可能性があるため、特別な理由がない限りtrueにすることをおすすめします。

12-6. WinFormsとWPFではどちらのSaveFileDialogを使うべき?

WinFormsアプリならSystem.Windows.Forms.SaveFileDialogを使います。

C#
using System.Windows.Forms;

WPFアプリならMicrosoft.Win32.SaveFileDialogを使います。

C#
using Microsoft.Win32;

どちらを使うべきかは、アプリのUIフレームワークで決まります。WinFormsアプリにWPF版を無理に使ったり、WPFアプリにWinForms版を混在させたりすると、参照やダイアログの所有関係が分かりにくくなります。基本的には、プロジェクトの種類に合わせて選びましょう。

まとめ

C#のSaveFileDialogは、ユーザーに保存先とファイル名を選ばせるための便利なダイアログです。WinFormsではSystem.Windows.Forms.SaveFileDialog、WPFではMicrosoft.Win32.SaveFileDialogを使います。

基本的な流れは、SaveFileDialogを作成し、FilterDefaultExtなどを設定し、ShowDialog()で表示し、ユーザーが保存を確定した場合だけFileNameからパスを取得して保存処理を行う、という形です。

特に重要なポイントは次の通りです。

  • SaveFileDialogは保存先を選ぶだけで、ファイル保存自体は別途実装する

  • WinFormsではDialogResult.OKで判定する

  • WPFではShowDialog() == trueで判定する

  • 拡張子設定にはFilterDefaultExtAddExtensionを使う

  • 上書き確認にはOverwritePromptを使う

  • 実際の保存処理はFile.WriteAllTextStreamWriterで行う

  • 保存時の例外処理を必ず入れる

実務では、初期ファイル名、保存形式、上書き確認、例外処理、保存完了メッセージまで含めて実装すると、ユーザーにとって分かりやすく安全な保存機能になります。