はじめに なぜ「パス結合」が業務でめちゃくちゃ大事なのか
ファイルやディレクトリを扱うコードを書くとき、ほぼ必ず出てくるのが「パスの結合」です。
たとえば「ログフォルダのパス」と「ファイル名」からフルパスを作る、「ルートフォルダ」と「サブフォルダ」と「ファイル名」をつなげる、といった処理です。
ここで安易に basePath + "\\" + fileName のような文字列連結をしてしまうと、区切り文字の重複や不足、OS 依存、絶対パスが混ざったときの挙動などで、地味にバグります。
C# には、これを安全にやるための Path.Combine という強力なメソッドが用意されています。
ここでは、プログラミング初心者向けに、「なぜ文字列連結ではなく Path.Combine を使うべきか」「どういう挙動をするのか」「どこでハマりやすいのか」を、例題を交えながら丁寧に解説していきます。
基本の API Path.Combine の使い方
最もシンプルな 2 つのパス結合
まずは一番シンプルな例から見てみましょう。
using System;
using System.IO;
class Program
{
static void Main()
{
string basePath = @"C:\logs";
string fileName = "app.log";
string fullPath = Path.Combine(basePath, fileName);
Console.WriteLine(fullPath); // C:\logs\app.log
}
}
C#Path.Combine は、「パスの区切り文字(\)をいい感じに補ってくれる」メソッドです。 basePath の末尾に \ が付いていてもいなくても、結果は同じになります。
string p1 = Path.Combine(@"C:\logs", "app.log"); // C:\logs\app.log
string p2 = Path.Combine(@"C:\logs\", "app.log"); // C:\logs\app.log
C#この「末尾の \ を気にしなくていい」というのが、業務コードではかなり効いてきます。
呼び出し側が「\ 付きかどうか」を意識しなくて済むので、バグの入り込む余地が減ります。
3 つ以上のパスを結合する
Path.Combine は、2 つだけでなく、3 つ、4 つ、配列でも結合できます。
string path1 = Path.Combine(@"C:\data", "import", "2025");
Console.WriteLine(path1); // C:\data\import\2025
string path2 = Path.Combine(
@"C:\data",
"import",
"2025",
"01");
Console.WriteLine(path2); // C:\data\import\2025\01
string[] segments = { @"C:\data", "import", "2025", "01", "sales.csv" };
string path3 = Path.Combine(segments);
Console.WriteLine(path3); // C:\data\import\2025\01\sales.csv
C#「ルート」「サブフォルダ」「さらにサブフォルダ」「ファイル名」といった構造を、そのまま引数の並びで表現できるので、コードの意図が読みやすくなります。
文字列連結と Path.Combine の違い
文字列連結で起こりがちなバグ
よくあるダメな例をあえて書いてみます。
string basePath = @"C:\logs";
string fileName = "app.log";
string fullPath = basePath + "\\" + fileName;
C#一見問題なさそうですが、次のようなケースで崩れます。
string basePath1 = @"C:\logs";
string basePath2 = @"C:\logs\";
string f1 = basePath1 + "\\" + "app.log"; // C:\logs\\app.log
string f2 = basePath2 + "\\" + "app.log"; // C:\logs\\\app.log
C#区切り文字が二重、三重になってしまいます。
Windows はある程度許容してくれますが、見た目も悪いし、パス比較などをするときに余計な差分になります。
逆に、区切りを付け忘れるとこうなります。
string basePath = @"C:\logs";
string fullPath = basePath + "app.log"; // C:\logsapp.log
C#これは完全に別のパスです。
こういう「人間のうっかり」を防ぐために、Path.Combine を使う価値があります。
Path.Combine がやってくれること
Path.Combine は、次のようなことを自動でやってくれます。
- 末尾・先頭の区切り文字を見て、必要な分だけ
\を挿入する - OS に応じた区切り文字(Windows なら
\)を使う - 2 個目以降に「ルート付きパス」が来た場合の扱いを決めてくれる(後述)
つまり、「パスの区切りを自分で考えない」ためのメソッドです。
パスは文字列ですが、「ただの文字列」として扱うと痛い目を見る、という感覚を持っておくといいです。
重要ポイント 2 個目以降に「絶対パス」が来たときの挙動
ここが初心者が一番ハマりやすいところなので、しっかり深掘りします。
2 個目以降にルート付きパスが来ると、前は無視される
Path.Combine の仕様として、「最初の引数以外にルート付きパス(絶対パス)が来た場合、それ以前の部分は無視される」というルールがあります。
例を見てみましょう。
string p = Path.Combine(
@"C:\base",
@"D:\other",
"file.txt");
Console.WriteLine(p); // D:\other\file.txt
C#C:\base は完全に無視されて、結果は D:\other\file.txt になります。
「全部つながる」と思っていると、ここで盛大にバグります。
もう一つ。
string p = Path.Combine(
@"C:\base",
@"\absolute\path",
"file.txt");
Console.WriteLine(p); // \absolute\path\file.txt
C#この場合も、C:\base は無視されて、結果は \absolute\path\file.txt になります。
つまり、「2 個目以降に絶対パスを渡すと、そこから先が新しいルートとして扱われる」ということです。
なぜこの仕様が危険になりうるのか
例えば、「ユーザー入力のパス」をそのまま Path.Combine に渡すようなコードを書いたとします。
string baseDir = @"C:\safe\root";
string userInput = textBoxPath.Text; // ユーザーが入力
string fullPath = Path.Combine(baseDir, userInput);
C#ユーザーが ..\..\Windows\system32 のような相対パスを入れてくるだけならまだしも、D:\ から始まる絶対パスを入れてきた場合、baseDir は完全に無視されます。
「必ず C:\safe\root 配下にしかアクセスさせないつもりだった」のに、
実際にはどこにでもアクセスできてしまう、というセキュリティ的な問題につながります。
実務では、「2 個目以降に絶対パスが来ないようにバリデーションする」「絶対パスが来たらエラーにする」といった対策を入れることが多いです。
実務で使えるパス結合ユーティリティ
「絶対パスを許さない」安全な Combine
業務ユーティリティとしては、「2 個目以降に絶対パスが来たら例外にする」ラッパーを用意しておくと安心です。
using System;
using System.IO;
using System.Linq;
public static class SafePath
{
public static string Combine(string basePath, params string[] segments)
{
if (string.IsNullOrWhiteSpace(basePath))
{
throw new ArgumentException("basePath が空です。", nameof(basePath));
}
if (segments == null || segments.Length == 0)
{
return basePath;
}
foreach (string seg in segments)
{
if (string.IsNullOrEmpty(seg))
{
continue;
}
if (Path.IsPathRooted(seg))
{
throw new ArgumentException(
$"絶対パスは結合できません: {seg}",
nameof(segments));
}
basePath = Path.Combine(basePath, seg);
}
return basePath;
}
}
C#ここでの重要ポイントは、Path.IsPathRooted を使って「絶対パスかどうか」を判定していることです。
絶対パスが混ざっていたら例外を投げることで、「気づかないうちに basePath が無視される」事故を防ぎます。
使い方の例は次の通りです。
class Program
{
static void Main()
{
string root = @"C:\data";
string p1 = SafePath.Combine(root, "import", "2025", "01");
Console.WriteLine(p1); // C:\data\import\2025\01
try
{
string p2 = SafePath.Combine(root, @"D:\other", "file.txt");
}
catch (ArgumentException ex)
{
Console.WriteLine("エラー: " + ex.Message);
}
}
}
C#こうしておくと、「このユーティリティを通している限り、必ず root 配下のパスになる」という前提でコードを書けます。
「末尾に必ずディレクトリ区切りを付ける」ユーティリティ
フォルダパスを扱うとき、「末尾に \ が付いているかどうか」を気にしたくない場面も多いです。
その場合は、Path.Combine と Path.DirectorySeparatorChar を組み合わせたユーティリティを用意しておくと便利です。
public static class PathEx
{
public static string EnsureTrailingSeparator(string directoryPath)
{
if (string.IsNullOrWhiteSpace(directoryPath))
{
throw new ArgumentException("ディレクトリパスが空です。", nameof(directoryPath));
}
directoryPath = Path.GetFullPath(directoryPath);
char sep = Path.DirectorySeparatorChar;
if (!directoryPath.EndsWith(sep.ToString()))
{
directoryPath += sep;
}
return directoryPath;
}
}
C#これを使うと、次のように書けます。
string dir1 = PathEx.EnsureTrailingSeparator(@"C:\data");
string dir2 = PathEx.EnsureTrailingSeparator(@"C:\data\");
Console.WriteLine(dir1); // C:\data\
Console.WriteLine(dir2); // C:\data\
C#このように、「フォルダパスは必ず末尾に区切り付き」というルールを決めておくと、
後続の処理(相対パス結合など)が書きやすくなります。
パス結合とプラットフォーム依存の話
区切り文字は OS によって違う
Windows では \、Unix 系(Linux, macOS)では / がパスの区切り文字です。
C# の Path.Combine は、実行環境に応じた区切り文字を自動で使ってくれます。
つまり、次のように書いても、Windows なら \、Linux なら / で結合されます。
string path = Path.Combine("data", "import", "2025");
Console.WriteLine(path); // 実行環境に応じた区切りで表示される
C#業務で Windows しか想定していないとしても、「区切り文字を自分で書かない」習慣を付けておくと、
将来の移植やテスト環境の違いに強くなります。
Path.DirectorySeparatorChar を意識する場面
自分でパス文字列を加工するときは、'\\' を直書きするのではなく、Path.DirectorySeparatorChar を使うと、OS に依存しないコードになります。
char sep = Path.DirectorySeparatorChar;
string path = "data" + sep + "import" + sep + "2025";
C#とはいえ、基本的には「結合は全部 Path.Combine に任せる」が正解で、
自分で区切り文字を扱うのは最小限にしたほうが安全です。
例外とエラー処理を意識したパス結合
どんなときに問題が起こるか
Path.Combine 自体は、単に文字列を結合するだけなので、
それ単体で例外を投げることはあまり多くありません。
ただし、結合結果を Path.GetFullPath に渡したり、
実際に File.Exists や Directory.CreateDirectory に渡したりするときに、次のような問題が表面化します。
パスが OS の制限より長すぎる。
パスに不正な文字が含まれている。
意図しない絶対パスが混ざっていて、想定外の場所を指している。
特に 3 つ目は、「Combine の仕様を知らないと気づきにくい」ので要注意です。
呼び出し側での扱い方の例
using System;
using System.IO;
class Program
{
static void Main()
{
string root = @"C:\data";
string sub = "import";
string file = "sales.csv";
try
{
string path = SafePath.Combine(root, sub, file);
Console.WriteLine("結合結果: " + path);
if (!File.Exists(path))
{
Console.WriteLine("ファイルが存在しません。");
}
}
catch (ArgumentException ex)
{
Console.WriteLine("パスの指定が不正です: " + ex.Message);
}
catch (PathTooLongException ex)
{
Console.WriteLine("パスが長すぎます: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("想定外のエラー: " + ex.Message);
}
}
}
C#実務では、「どの basePath とどのセグメントを結合しようとして失敗したか」をログに残しておくと、
後から原因を追いやすくなります。
まとめ 実務で使える「パス結合」ユーティリティの考え方
パス結合は、「ファイル・ディレクトリ操作のすべての入り口」と言っていいくらい、頻繁に出てきます。
だからこそ、「なんとなく文字列連結」で済ませるのではなく、きちんと設計しておく価値があります。
押さえておきたいポイントを整理すると、こうなります。
Path.Combine を使えば、末尾の \ を気にせず安全に結合できる。
2 個目以降に絶対パス(ルート付きパス)が来ると、それ以前は無視される仕様を必ず理解しておく。
ユーザー入力など、信頼できない文字列をそのまま Combine しない。必要なら Path.IsPathRooted でチェックする。
「絶対パスを許さない SafePath.Combine」のようなラッパーを用意して、ルート配下からはみ出さない設計にする。
区切り文字は自分で書かず、基本は Path.Combine に任せる。どうしても必要なら Path.DirectorySeparatorChar を使う。
ここまで押さえておけば、「パス結合まわりでハマる時間」はかなり減ります。
