C#でアプリケーションパスを取得する方法|実行ファイル・起動フォルダ・相対パスの違いを解説

はじめに

C#で設定ファイル、ログファイル、画像ファイル、外部DLLなどを扱うとき、「アプリケーションパスを取得したい」という場面はよくあります。しかし、C#における「アプリケーションパス」は1種類だけではありません。

たとえば、次のようなパスは似ているようで意味が異なります。

  • 実行ファイルが置かれているフォルダ

  • アプリケーションを起動したときの作業フォルダ

  • 現在のカレントディレクトリ

  • 実行中のexeファイルそのもののパス

  • アセンブリDLLの配置場所

  • ASP.NET Coreのコンテンツルート

この違いを理解せずに相対パスを使うと、「開発環境では動くのにリリース後にファイルが見つからない」「タスクスケジューラから実行するとパスが変わる」「binフォルダ配下を参照してしまう」といった問題が起こります。

この記事では、C#でアプリケーションパスを取得する代表的な方法を、実行ファイルの場所、起動フォルダ、相対パスの違いに分けて解説します。

1. C#でアプリケーションパスを取得したいときの基本

1-1. アプリケーションパスとは何か

C#でいう「アプリケーションパス」は、文脈によって指す場所が変わります。

多くの場合は「exeファイルがあるフォルダ」を意味しますが、コード上ではそれとは別に「現在の作業ディレクトリ」や「アセンブリが読み込まれた場所」も取得できます。

たとえば、次のような目的では取得すべきパスが異なります。

やりたいこと取得すべきパス
exeと同じフォルダの設定ファイルを読む実行ファイルのあるフォルダ
コマンド実行時の作業フォルダを知るカレントディレクトリ
実行中のexeファイル名まで含めたパスを知るプロセスの実行ファイルパス
DLL自身の場所を知るAssembly.Location
ASP.NET Coreでappsettings.jsonの基準位置を知るContentRootPath

つまり、「C#でアプリケーションパスを取得する」といっても、目的に応じて適切なAPIを選ぶ必要があります。

1-2. 実行ファイルの場所・起動フォルダ・カレントディレクトリの違い

特に混同しやすいのが、次の3つです。

まず、実行ファイルの場所とは、アプリケーションのexeやdllが配置されているフォルダです。たとえば、Windowsアプリを C:\Apps\MyApp\MyApp.exe に配置している場合、実行ファイルのあるフォルダは C:\Apps\MyApp\ です。

次に、起動フォルダはアプリケーションを起動したときに作業ディレクトリとして指定されたフォルダを指すことがあります。ショートカットの「作業フォルダー」やタスクスケジューラの「開始」欄によって変わることがあります。

そして、カレントディレクトリは、プロセスが現在基準としている作業ディレクトリです。Environment.CurrentDirectoryDirectory.GetCurrentDirectory() で取得できます。Microsoftのドキュメントでも、Environment.CurrentDirectory は現在の作業ディレクトリの完全修飾パスを取得または設定するプロパティと説明されています。Microsoft Learn

この3つは同じになることもありますが、常に同じとは限りません。

1-3. 取得したいパスによって使うメソッドが変わる理由

C#にはパス取得用のAPIが複数あります。これは、アプリケーションの実行形態が1つではないためです。

コンソールアプリ、Windows Forms、WPF、ASP.NET Core、Windowsサービス、単一ファイル配布アプリでは、パスの考え方が異なります。

たとえば、exeと同じフォルダのファイルを読み込みたいなら、基本的には AppContext.BaseDirectory を使うのが扱いやすいです。一方、ユーザーがコマンドプロンプトで作業しているフォルダを基準にファイルを読みたい場合は、Directory.GetCurrentDirectory() を使うほうが自然です。

目的に合わないメソッドを使うと、ファイルの参照先がずれてしまいます。

2. C#で実行ファイルのあるフォルダを取得する方法

2-1. AppContext.BaseDirectoryで取得する

現在の.NETで、実行ファイルのあるフォルダを取得したい場合にまず検討したいのが AppContext.BaseDirectory です。

C#
string baseDir = AppContext.BaseDirectory;

Console.WriteLine(baseDir);

AppContext.BaseDirectory は、アセンブリ解決に使われるベースディレクトリのパスを返します。Microsoftのドキュメントでは、現在のアプリケーションドメインの AppDomain.BaseDirectory に対応する値であり、.NET 5以降の単一ファイル配布ではホスト実行ファイルを含むディレクトリを返すと説明されています。Microsoft Learn

たとえば、アプリケーションが次の場所に配置されているとします。

C:\Apps\SampleApp\SampleApp.exe

この場合、AppContext.BaseDirectory は通常、次のような値になります。

C:\Apps\SampleApp\

exeと同じフォルダにある設定ファイルを読み込むなら、次のように書けます。

C#
using System;
using System.IO;

string configPath = Path.Combine(AppContext.BaseDirectory, "settings.json");

if (File.Exists(configPath))
{
string json = File.ReadAllText(configPath);
Console.WriteLine(json);
}
else
{
Console.WriteLine("設定ファイルが見つかりません。");
}

ポイントは、文字列連結ではなく Path.Combine を使うことです。これにより、区切り文字の有無を気にせず安全にパスを組み立てられます。

2-2. AppDomain.CurrentDomain.BaseDirectoryで取得する

.NET Framework時代からよく使われてきた方法が AppDomain.CurrentDomain.BaseDirectory です。

C#
string baseDir = AppDomain.CurrentDomain.BaseDirectory;

Console.WriteLine(baseDir);

このメソッドも、アプリケーションのベースディレクトリを取得するために使われます。AppContext.BaseDirectory と近い値を返すことが多く、従来の.NET Frameworkアプリではよく見かける書き方です。

C#
string logPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"logs",
"app.log"
);

Console.WriteLine(logPath);

現在の.NETでは、特別な理由がなければ AppContext.BaseDirectory を使うとシンプルです。既存の.NET Frameworkコードでは AppDomain.CurrentDomain.BaseDirectory が使われていることも多いため、保守時には意味を理解しておくとよいでしょう。

2-3. Assembly.GetExecutingAssembly().Locationで取得する

Assembly.GetExecutingAssembly().Location を使うと、実行中のアセンブリファイルのパスを取得できます。

C#
using System.Reflection;

string assemblyPath = Assembly.GetExecutingAssembly().Location;

Console.WriteLine(assemblyPath);

この値はフォルダではなく、アセンブリファイル名まで含むパスです。

C:\Apps\SampleApp\SampleApp.dll

フォルダだけが必要な場合は、Path.GetDirectoryName を使います。

C#
using System.IO;
using System.Reflection;

string assemblyPath = Assembly.GetExecutingAssembly().Location;
string? assemblyDir = Path.GetDirectoryName(assemblyPath);

Console.WriteLine(assemblyDir);

ただし、Assembly.Location は「アプリケーションの実行フォルダ」を取得するための万能な方法ではありません。Microsoftのドキュメントでは、Assembly.Location はマニフェストを含む読み込み済みファイルの場所を返し、バイト配列から読み込まれた場合などは空文字列を返すことがあると説明されています。Microsoft Learn

さらに、単一ファイル配布では注意が必要です。.NETの単一ファイル配布では、バンドル内のアセンブリに対して Assembly.Location が空文字列を返す場合があり、実行ファイルのあるフォルダを知りたい場合は AppContext.BaseDirectory が推奨されています。Microsoft Learn+1

2-4. 実行ファイルのパスと実行フォルダのパスの違い

実行ファイルのパスと実行フォルダのパスは、似ていますが別物です。

実行ファイルのパス:
C:\Apps\SampleApp\SampleApp.exe

実行フォルダのパス:
C:\Apps\SampleApp\

設定ファイルやログファイルをexeと同じ場所から読みたい場合は、フォルダのパスが必要です。一方、起動した実行ファイル名まで必要な場合は、ファイル名を含むパスを取得します。

.NET 6以降で実行ファイル名まで含めたパスを取得したい場合は、Environment.ProcessPath を使えます。

C#
string? exePath = Environment.ProcessPath;

Console.WriteLine(exePath);

Environment.ProcessPath は、現在実行中のプロセスを開始した実行可能ファイルのパスを返し、取得できない場合は null を返します。Microsoft Learn

フォルダだけが必要なら、次のようにします。

C#
string? exePath = Environment.ProcessPath;
string? exeDir = exePath is null ? null : Path.GetDirectoryName(exePath);

Console.WriteLine(exeDir);

ただし、exeの隣にあるファイルを扱うだけなら、基本的には AppContext.BaseDirectory のほうが簡潔です。

3. C#で起動フォルダ・カレントディレクトリを取得する方法

3-1. Environment.CurrentDirectoryで取得する

現在のカレントディレクトリを取得するには、Environment.CurrentDirectory を使います。

C#
string currentDir = Environment.CurrentDirectory;

Console.WriteLine(currentDir);

Environment.CurrentDirectory は取得だけでなく設定もできます。

C#
Environment.CurrentDirectory = @"C:\Work";

Console.WriteLine(Environment.CurrentDirectory);

ただし、カレントディレクトリを変更すると、相対パスを使っている他の処理にも影響します。アプリケーション全体でファイルアクセスの基準が変わるため、安易に変更しないほうが安全です。

3-2. Directory.GetCurrentDirectoryで取得する

Directory.GetCurrentDirectory() でも現在のカレントディレクトリを取得できます。

C#
using System.IO;

string currentDir = Directory.GetCurrentDirectory();

Console.WriteLine(currentDir);

Microsoftのドキュメントでは、Directory.GetCurrentDirectory() が返す現在のディレクトリは、プロセスが開始された元のディレクトリとは異なるものとして説明されています。Microsoft Learn

つまり、アプリケーション起動後に Directory.SetCurrentDirectory()Environment.CurrentDirectory で変更される可能性があります。

C#
using System;
using System.IO;

Console.WriteLine("変更前:");
Console.WriteLine(Directory.GetCurrentDirectory());

Directory.SetCurrentDirectory(@"C:\Temp");

Console.WriteLine("変更後:");
Console.WriteLine(Directory.GetCurrentDirectory());

3-3. 実行ファイルの場所とカレントディレクトリが異なるケース

実行ファイルの場所とカレントディレクトリは、次のようなケースで異なることがあります。

たとえば、コマンドプロンプトで次のように実行した場合です。

cmd
cd C:\Work
C:\Apps\SampleApp\SampleApp.exe

このとき、実行ファイルは C:\Apps\SampleApp\ にありますが、カレントディレクトリは C:\Work になる可能性があります。

また、Windowsのショートカットから起動する場合、ショートカットの「作業フォルダー」に指定された場所がカレントディレクトリになることがあります。タスクスケジューラでも「開始」欄の設定によって、カレントディレクトリが変わることがあります。

そのため、次の2つは同じ値になるとは限りません。

C#
Console.WriteLine(AppContext.BaseDirectory);
Console.WriteLine(Directory.GetCurrentDirectory());

3-4. カレントディレクトリを使うときの注意点

カレントディレクトリは、ユーザーの操作や起動方法に影響されやすいパスです。

そのため、アプリケーションに同梱した設定ファイルや画像ファイルを読み込む目的では、カレントディレクトリを基準にしないほうが安全です。

避けたい例は次のようなコードです。

C#
string text = File.ReadAllText("settings.json");

このコードはシンプルですが、settings.json を探す基準がカレントディレクトリになります。Visual Studioから実行したとき、コマンドラインから実行したとき、ショートカットから実行したときで参照先が変わる可能性があります。

exeと同じフォルダのファイルを読みたいなら、次のように書くほうが安全です。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");
string text = File.ReadAllText(path);

4. アプリケーション種別ごとのパス取得方法

4-1. コンソールアプリでアプリケーションパスを取得する

コンソールアプリで、exeまたはdllの配置フォルダを取得したい場合は、基本的に AppContext.BaseDirectory を使います。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string appDir = AppContext.BaseDirectory;
string filePath = Path.Combine(appDir, "data.txt");

Console.WriteLine($"アプリケーションフォルダ: {appDir}");
Console.WriteLine($"読み込み対象: {filePath}");
}
}

一方、ユーザーがコマンドを実行した作業フォルダを基準にしたい場合は、Directory.GetCurrentDirectory() を使います。

C#
string workDir = Directory.GetCurrentDirectory();
string inputPath = Path.Combine(workDir, "input.txt");

Console.WriteLine(inputPath);

コマンドラインツールでは、カレントディレクトリを基準にする設計も自然です。たとえば、ユーザーが現在いるフォルダのファイルを処理するツールなら、Directory.GetCurrentDirectory() が適しています。

4-2. Windows FormsでApplication.StartupPathを使う

Windows Formsでは、Application.StartupPath を使ってアプリケーションを開始した実行ファイルのパスを取得できます。

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

string startupPath = Application.StartupPath;
string imagePath = Path.Combine(startupPath, "Images", "logo.png");

MessageBox.Show(imagePath);

Application.StartupPath は、アプリケーションを開始した実行ファイルのパスを返します。ClickOnceで配置した場合など、配置方法によってパスが変わることがある点には注意が必要です。Microsoft Learn

Windows Formsでは昔から Application.StartupPath がよく使われますが、.NET 6以降のWindows Formsでも、共通的な書き方にしたい場合は AppContext.BaseDirectory を使う選択肢もあります。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");

4-3. WPFアプリで実行フォルダを取得する

WPFアプリでも、実行フォルダを取得するなら AppContext.BaseDirectory が使いやすいです。

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

string appDir = AppContext.BaseDirectory;
string settingPath = Path.Combine(appDir, "settings.json");

MessageBox.Show(settingPath);

WPFでは、画像やXAMLリソースをアプリケーションリソースとして埋め込む場合もあります。その場合、ファイルパスではなくリソースURIで扱うこともあります。

ただし、アプリケーション外部の設定ファイルやログファイル、ユーザーが差し替えるファイルを扱う場合は、AppContext.BaseDirectory やユーザーデータフォルダなど、用途に応じた保存先を選ぶ必要があります。

4-4. ASP.NET・Windowsサービスで注意すべきパスの違い

ASP.NET Coreでは、通常のデスクトップアプリのように「exeと同じフォルダ」を基準に考えるだけでは不十分です。

ASP.NET Coreでは、コンテンツルートやWebルートという考え方があります。静的ファイルの既定配置場所は {CONTENT ROOT}/wwwroot であり、{CONTENT ROOT} はアプリのコンテンツルートを表します。Microsoft Learn

ASP.NET Coreでファイルパスを扱う場合は、IWebHostEnvironmentIHostEnvironment を使うのが一般的です。

C#
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.IO;

public class HomeController : Controller
{
private readonly IWebHostEnvironment _env;

public HomeController(IWebHostEnvironment env)
{
_env = env;
}

public IActionResult Index()
{
string contentRoot = _env.ContentRootPath;
string webRoot = _env.WebRootPath;

string filePath = Path.Combine(contentRoot, "App_Data", "data.json");

return Content(filePath);
}
}

Windowsサービスでは、カレントディレクトリが想定と異なる場所になることがあります。ASP.NET CoreをWindowsサービスとしてホストする場合、Microsoftのドキュメントではコンテンツルートを AppContext.BaseDirectory に設定することが説明されています。Microsoft Learn

サービスやWebアプリでは、カレントディレクトリに依存せず、明示的な基準パスを使うことが重要です。

5. 相対パスを安全に扱う方法

5-1. 相対パスはどのフォルダを基準に解決されるのか

C#で相対パスを指定した場合、多くのファイル操作ではカレントディレクトリを基準に解決されます。

たとえば、次のコードを見てください。

C#
string text = File.ReadAllText("data.txt");

このコードは、ソースコードのあるフォルダやプロジェクトフォルダから data.txt を読むわけではありません。基本的には、現在のカレントディレクトリを基準に data.txt を探します。

そのため、実行環境によっては次のような違いが出ます。

Visual Studioデバッグ実行:
プロジェクト\bin\Debug\net8.0\data.txt

コマンドプロンプトから実行:
現在cdしているフォルダ\data.txt

ショートカットから実行:
ショートカットの作業フォルダー\data.txt

相対パスは便利ですが、基準位置を理解して使わないと、ファイルが見つからない原因になります。

5-2. Path.Combineでファイルパスを組み立てる

ファイルパスを組み立てるときは、文字列連結ではなく Path.Combine を使います。

避けたい例です。

C#
string path = AppContext.BaseDirectory + "\\settings.json";

推奨される例です。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");

サブフォルダを含める場合も簡単です。

C#
string path = Path.Combine(
AppContext.BaseDirectory,
"Resources",
"Images",
"logo.png"
);

Path.Combine を使うと、区切り文字の重複や不足を避けやすくなります。また、Windows以外の環境で動かす可能性がある場合にも、プラットフォーム差を吸収しやすくなります。

5-3. 設定ファイル・ログファイル・画像ファイルを読み込む例

exeと同じフォルダにある設定ファイルを読み込む例です。

C#
using System;
using System.IO;

string configPath = Path.Combine(AppContext.BaseDirectory, "settings.json");

if (!File.Exists(configPath))
{
throw new FileNotFoundException("設定ファイルが見つかりません。", configPath);
}

string json = File.ReadAllText(configPath);
Console.WriteLine(json);

ログファイルを logs フォルダに書き込む例です。

C#
using System;
using System.IO;

string logDir = Path.Combine(AppContext.BaseDirectory, "logs");
Directory.CreateDirectory(logDir);

string logPath = Path.Combine(logDir, "app.log");
File.AppendAllText(logPath, $"{DateTime.Now}: アプリを起動しました。{Environment.NewLine}");

画像ファイルを読み込む例です。

C#
using System.IO;

string imagePath = Path.Combine(AppContext.BaseDirectory, "Images", "logo.png");

if (File.Exists(imagePath))
{
byte[] bytes = File.ReadAllBytes(imagePath);
}

ただし、実行フォルダ配下への書き込みは、配置先によっては権限エラーになることがあります。ログやユーザー設定を書き込む場合は、AppData などユーザーごとの書き込み可能な場所を使う設計も検討してください。

5-4. 相対パスでファイルが見つからない原因と対処法

相対パスでファイルが見つからない主な原因は、基準フォルダの思い違いです。

よくある原因は次のとおりです。

原因対処法
カレントディレクトリが想定と違うDirectory.GetCurrentDirectory() を出力して確認する
ファイルが出力フォルダにコピーされていないVisual Studioのファイルプロパティでコピー設定を確認する
exeと同じフォルダを見ているつもりで相対パスを使っているAppContext.BaseDirectoryPath.Combine を使う
タスクスケジューラ実行時だけ失敗する「開始」欄を設定するか、絶対パスを使う
リリース後に配置構成が変わった実行環境のフォルダ構成に合わせてパスを見直す

デバッグ時には、まず次の情報をログに出すと原因を特定しやすくなります。

C#
Console.WriteLine($"AppContext.BaseDirectory: {AppContext.BaseDirectory}");
Console.WriteLine($"CurrentDirectory: {Directory.GetCurrentDirectory()}");
Console.WriteLine($"ProcessPath: {Environment.ProcessPath}");

6. パス取得メソッドの比較と使い分け

6-1. AppContext.BaseDirectoryとAppDomain.CurrentDomain.BaseDirectoryの違い

AppContext.BaseDirectoryAppDomain.CurrentDomain.BaseDirectory は、どちらもアプリケーションのベースディレクトリを取得するために使われます。

C#
Console.WriteLine(AppContext.BaseDirectory);
Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory);

多くのアプリでは同じような値を返しますが、現在の.NETでは AppContext.BaseDirectory のほうがシンプルで使いやすい選択肢です。Microsoftのドキュメントでも、AppContext.BaseDirectory は現在のアプリケーションドメインの AppDomain.BaseDirectory に対応すると説明されています。Microsoft Learn

新規開発で「exeと同じフォルダ」「アプリの配置フォルダ」を取得したい場合は、まず AppContext.BaseDirectory を使うとよいでしょう。

6-2. Assembly.Locationと実行フォルダ取得の違い

Assembly.Location は、アセンブリファイルの場所を取得するAPIです。

C#
string location = Assembly.GetExecutingAssembly().Location;

この値は、実行ファイルのあるフォルダそのものではなく、読み込まれているアセンブリファイルのパスです。

さらに、単一ファイル配布や動的読み込みなどでは、期待した値にならない場合があります。単一ファイル配布でアセンブリのファイルパスに依存するコードは警告対象になることがあり、実行ファイルの隣にあるファイルへアクセスする用途では AppContext.BaseDirectory の利用が案内されています。Microsoft Learn+1

そのため、外部プラグインや特定DLLの場所を知りたい場合は Assembly.Location、アプリの配置フォルダを知りたい場合は AppContext.BaseDirectory、というように使い分けます。

6-3. Environment.CurrentDirectoryとの違い

Environment.CurrentDirectory は、実行ファイルの場所ではなく、現在の作業ディレクトリです。

C#
Console.WriteLine(Environment.CurrentDirectory);

AppContext.BaseDirectoryEnvironment.CurrentDirectory の違いは重要です。

C#
Console.WriteLine("実行フォルダ:");
Console.WriteLine(AppContext.BaseDirectory);

Console.WriteLine("カレントディレクトリ:");
Console.WriteLine(Environment.CurrentDirectory);

Visual Studioから起動したときは似た値になることもありますが、コマンドライン、ショートカット、タスクスケジューラ、Windowsサービスでは異なることがあります。

同梱ファイルを読むなら AppContext.BaseDirectory、ユーザーの作業フォルダを基準にするなら Environment.CurrentDirectory または Directory.GetCurrentDirectory() と考えると整理しやすいです。

6-4. 目的別におすすめの取得方法

目的別のおすすめは次のとおりです。

目的おすすめ
exeと同じフォルダを取得したいAppContext.BaseDirectory
.NET Frameworkの既存コードでベースディレクトリを取得したいAppDomain.CurrentDomain.BaseDirectory
現在の作業フォルダを取得したいDirectory.GetCurrentDirectory()
カレントディレクトリを取得・変更したいEnvironment.CurrentDirectory
実行ファイル名まで含めたパスを取得したいEnvironment.ProcessPath
Windows Formsで起動パスを取得したいApplication.StartupPath
アセンブリDLLの場所を取得したいAssembly.GetExecutingAssembly().Location
ASP.NET Coreのコンテンツルートを取得したいIHostEnvironment.ContentRootPath

迷った場合は、「自分が知りたいのは実行ファイルの場所なのか、作業フォルダなのか」を先に決めることが重要です。

7. C#でアプリケーションパスを取得するときの注意点

7-1. デバッグ実行時とリリース実行時でパスが変わる

Visual Studioでデバッグ実行すると、アプリケーションは通常、プロジェクト直下ではなく bin\Debug 配下から実行されます。

たとえば、.NET 8のコンソールアプリでは次のような場所になります。

プロジェクトフォルダ\bin\Debug\net8.0\

リリースビルドでは次のようになります。

プロジェクトフォルダ\bin\Release\net8.0\

そのため、AppContext.BaseDirectory はプロジェクトフォルダではなく、ビルド出力先を返します。

「プロジェクト直下の files フォルダを読みたい」と思っていても、実行時には bin\Debug\net8.0\files を見に行くことがあります。

対処法は、必要なファイルを出力フォルダにコピーすることです。Visual Studioでは、対象ファイルのプロパティで「出力ディレクトリにコピー」を設定できます。

7-2. ショートカット・タスクスケジューラ実行時の違い

ショートカットからアプリケーションを起動する場合、ショートカットの「作業フォルダー」によってカレントディレクトリが変わることがあります。

また、タスクスケジューラでは「操作」設定にある「開始」欄が空の場合、想定外のフォルダがカレントディレクトリになることがあります。

そのため、タスクスケジューラで実行するアプリでは、次のような相対パス指定は避けるべきです。

C#
File.WriteAllText("log.txt", "ログ");

代わりに、基準パスを明示します。

C#
string logPath = Path.Combine(AppContext.BaseDirectory, "log.txt");
File.WriteAllText(logPath, "ログ");

または、タスクスケジューラ側で開始フォルダを正しく設定します。

7-3. 単一ファイル配布・自己完結型アプリでの注意点

.NETでは、アプリを単一ファイルとして発行できます。この場合、従来のように複数のDLLがそのままディスク上に並ぶとは限りません。

単一ファイル配布では、アセンブリの物理的なファイルパスに依存するコードが期待どおりに動かない場合があります。Microsoftのドキュメントでは、単一ファイルとして発行したアプリで、埋め込まれたアセンブリに対する Assembly.Location が空文字列を返すことが説明されています。Microsoft Learn

exeの隣にあるファイルへアクセスしたい場合は、AppContext.BaseDirectory を使うのが安全です。

C#
string filePath = Path.Combine(AppContext.BaseDirectory, "settings.json");

単一ファイル配布では、「外部ファイルとして配置するもの」と「埋め込みリソースにするもの」を分けて設計することが大切です。

7-4. ファイルアクセス権限と書き込み先の注意点

アプリケーションの実行フォルダは、必ずしも書き込み可能とは限りません。

たとえば、次のような場所にアプリをインストールしている場合、通常ユーザーでは書き込み権限がないことがあります。

C:\Program Files\MyApp\

この場所にログファイルや設定ファイルを書き込もうとすると、アクセス拒否の例外が発生する可能性があります。

C#
string logPath = Path.Combine(AppContext.BaseDirectory, "app.log");
File.AppendAllText(logPath, "ログ");

読み取り専用の設定ファイルなら実行フォルダに配置してもよいですが、ユーザーが変更する設定やログは、ユーザーごとのアプリケーションデータフォルダに保存するほうが安全です。

C#
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string appDir = Path.Combine(appData, "MyApp");

Directory.CreateDirectory(appDir);

string logPath = Path.Combine(appDir, "app.log");
File.AppendAllText(logPath, "ログ");

8. よくあるエラーと解決方法

8-1. ファイルが存在しないエラーが出る場合

FileNotFoundException や「ファイルが見つかりません」というエラーが出る場合は、まず実際にどのパスを見に行っているか確認します。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");

Console.WriteLine(path);
Console.WriteLine(File.Exists(path));

相対パスを使っている場合は、カレントディレクトリも確認します。

C#
Console.WriteLine(Directory.GetCurrentDirectory());

よくある原因は、ファイルが出力フォルダにコピーされていないことです。Visual Studio上のプロジェクトにファイルが存在していても、実行時の bin\Debugbin\Release にコピーされていなければ読み込めません。

8-2. 相対パスが想定外の場所を参照する場合

次のようなコードは、カレントディレクトリを基準にファイルを探します。

C#
string text = File.ReadAllText(@"data\sample.txt");

想定外の場所を参照している場合は、絶対パスに変換して確認すると分かりやすいです。

C#
string relativePath = @"data\sample.txt";
string fullPath = Path.GetFullPath(relativePath);

Console.WriteLine(fullPath);

exeと同じ場所を基準にしたい場合は、次のように修正します。

C#
string fullPath = Path.Combine(AppContext.BaseDirectory, "data", "sample.txt");

相対パスの基準を曖昧にしないことが、ファイルアクセスのトラブルを減らすポイントです。

8-3. binフォルダ配下のパスになってしまう場合

Visual Studioから実行すると、AppContext.BaseDirectoryApplication.StartupPathbin\Debugbin\Release 配下を返します。

これは異常ではありません。C#アプリはビルドされた出力ファイルを実行しているため、実行フォルダが bin 配下になるのは自然な動作です。

たとえば、次のようなパスです。

C:\Projects\SampleApp\SampleApp\bin\Debug\net8.0\

プロジェクトフォルダのファイルを直接参照するのではなく、必要なファイルを出力フォルダにコピーする構成にするのが基本です。

.csproj に次のように設定することもできます。

XML
<ItemGroup>
<None Update="settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

この設定により、settings.json がビルド出力先にコピーされます。

8-4. 設定ファイルやリソースファイルを正しく配置する方法

設定ファイルやリソースファイルを正しく扱うには、ファイルの性質に応じて配置方法を分けます。

読み取り専用の初期設定ファイルであれば、出力フォルダにコピーして AppContext.BaseDirectory から読む方法が分かりやすいです。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");

ユーザーが変更する設定ファイルであれば、初回起動時に AppData へコピーして、以後は AppData 側を読み書きする方法が安全です。

C#
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string appDir = Path.Combine(appData, "MyApp");
Directory.CreateDirectory(appDir);

string userSettingPath = Path.Combine(appDir, "settings.json");

画像やテンプレートなど、アプリに固定で含めたいファイルは、出力フォルダにコピーするか、埋め込みリソースとして扱う方法があります。

外部ファイルとしてユーザーに差し替えさせたいなら出力フォルダや専用フォルダ、アプリ内部で固定的に使うだけなら埋め込みリソース、というように使い分けるとよいでしょう。

9. C#のアプリケーションパス取得でよくある質問

9-1. 実行ファイル名まで含めたパスを取得するには?

.NET 6以降であれば、Environment.ProcessPath を使うのが簡単です。

C#
string? exePath = Environment.ProcessPath;

Console.WriteLine(exePath);

取得できる値の例です。

C:\Apps\SampleApp\SampleApp.exe

取得できない環境では null になる可能性があるため、nullチェックを入れると安全です。

C#
string? exePath = Environment.ProcessPath;

if (exePath is not null)
{
Console.WriteLine(exePath);
}

.NET Frameworkなどで別の方法が必要な場合は、Process.GetCurrentProcess().MainModule.FileName が使われることもあります。ただし、現在の.NETでは実行ファイルパスの取得に Environment.ProcessPath を使うことが推奨される場面が増えています。Microsoftのコード分析ルールでも、起動したファイルのパス取得には Environment.ProcessPath の利用が案内されています。Microsoft Learn

9-2. exeと同じフォルダのファイルを読み込むには?

AppContext.BaseDirectoryPath.Combine を使います。

C#
string path = Path.Combine(AppContext.BaseDirectory, "data.txt");

string text = File.ReadAllText(path);

サブフォルダにある場合は、次のようにします。

C#
string path = Path.Combine(AppContext.BaseDirectory, "Data", "data.txt");

string text = File.ReadAllText(path);

ファイルが存在するか確認するなら、次のように書きます。

C#
string path = Path.Combine(AppContext.BaseDirectory, "Data", "data.txt");

if (File.Exists(path))
{
string text = File.ReadAllText(path);
Console.WriteLine(text);
}
else
{
Console.WriteLine($"ファイルが見つかりません: {path}");
}

9-3. プロジェクトフォルダのパスを取得できる?

実行中のアプリから「Visual Studioのプロジェクトフォルダ」を取得する標準的な考え方は、基本的にはおすすめされません。

なぜなら、リリース後のアプリにはプロジェクトフォルダという概念がないからです。アプリはビルド出力先やインストール先から実行されます。

開発中にだけプロジェクトフォルダのファイルを参照したい場合もありますが、本番動作を考えるなら、必要なファイルを出力フォルダにコピーする構成にするべきです。

たとえば、settings.json をプロジェクトに含めて、ビルド時に出力先へコピーします。

XML
<ItemGroup>
<None Update="settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

そのうえで、実行時は次のように読み込みます。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");

どうしても開発時だけプロジェクトフォルダを参照したい場合は、環境変数や設定ファイルで明示的にパスを渡すほうが安全です。

9-4. .NET Frameworkと.NET 6以降で違いはある?

違いはあります。

.NET Frameworkでは、AppDomain.CurrentDomain.BaseDirectory や Windows Formsの Application.StartupPath がよく使われていました。

一方、.NET 6以降の新しいアプリでは、実行フォルダの取得には AppContext.BaseDirectory、実行ファイル名まで含むパスには Environment.ProcessPath を使うと分かりやすいです。

また、.NET Core以降では単一ファイル配布や自己完結型配布など、配置方法の選択肢が増えています。そのため、Assembly.Location のようなアセンブリの物理パスに依存するコードは、実行形態によって期待どおりにならない場合があります。

新規開発では、次のように考えるとよいでしょう。

対象基本方針
.NET FrameworkAppDomain.CurrentDomain.BaseDirectoryApplication.StartupPath
.NET 6以降AppContext.BaseDirectory
実行ファイル名まで必要Environment.ProcessPath
カレントディレクトリが必要Directory.GetCurrentDirectory()
単一ファイル配布Assembly.Location 依存を避け、AppContext.BaseDirectory を使う

まとめ

C#でアプリケーションパスを取得する方法は複数ありますが、重要なのは「何のパスが必要なのか」を明確にすることです。

exeと同じフォルダの設定ファイルや画像ファイルを読み込みたい場合は、基本的に AppContext.BaseDirectory を使うのがおすすめです。

C#
string path = Path.Combine(AppContext.BaseDirectory, "settings.json");

現在の作業フォルダを取得したい場合は、Directory.GetCurrentDirectory()Environment.CurrentDirectory を使います。

C#
string currentDir = Directory.GetCurrentDirectory();

実行ファイル名まで含めたパスが必要な場合は、.NET 6以降なら Environment.ProcessPath が使えます。

C#
string? exePath = Environment.ProcessPath;

Windows Formsでは Application.StartupPath、ASP.NET Coreでは IHostEnvironment.ContentRootPathIWebHostEnvironment.WebRootPath など、アプリケーション種別に応じた取得方法もあります。

相対パスでファイルが見つからないトラブルの多くは、実行フォルダとカレントディレクトリを混同していることが原因です。設定ファイルやログファイルを扱うときは、Path.Combine を使って基準パスを明示し、どのフォルダを参照しているのかを分かりやすくしておきましょう。