C# Tips | ファイル・ディレクトリ操作:絶対パス→相対パス

C sharp C#
スポンサーリンク

はじめに なぜ「絶対パス→相対パス」が必要になるのか

さっきまでは「相対パス → 絶対パス」をやりましたが、現場ではその逆、
「絶対パス → 相対パス」に変換したい場面もかなり多いです。

例えばこんなケースです。

  • 設定ファイルに「環境に依存しないパス」を書きたいので、相対パスで保存したい
  • GUI ツールでファイルを選んでもらい、その場所を「あるルートフォルダからの相対パス」として記録したい
  • Git リポジトリ内のファイルパスを、リポジトリルートからの相対パスで管理したい

こういうとき、「絶対パスのまま保存してしまう」と、
環境が変わった瞬間に使えなくなります(インストール先ドライブが変わる、ユーザー名が変わるなど)。

そこで出てくるのが「絶対パス → 相対パス」のユーティリティです。
ここでは、C# 初心者向けに、Path.GetRelativePath を中心に、考え方と実務での使い方を丁寧に解説していきます。


基本の考え方 絶対パスと相対パスの関係

「基準ディレクトリ」がないと相対パスは作れない

まず大事な前提から。

相対パスは、「ある基準ディレクトリから見た位置」を表すパスです。
つまり、「どこから見て相対なのか」が決まっていないと、相対パスは作れません。

例えば、次の絶対パスがあるとします。

  • 基準ディレクトリ: C:\Apps\MyTool
  • 対象ファイル: C:\Apps\MyTool\logs\app.log

このとき、「基準から見た相対パス」は logs\app.log になります。

一方、基準ディレクトリが C:\Apps なら、
相対パスは MyTool\logs\app.log になります。

同じ絶対パスでも、「どこを基準にするか」で相対パスは変わる、ということです。
ここをちゃんと意識しておくと、後の設計がブレません。

C# でのキーワードは「basePath」と「path」

C# の世界では、
「基準ディレクトリ」を basePath
「相対にしたい絶対パス」を path
と呼ぶことが多いです。

この 2 つが揃って、初めて「絶対 → 相対」が定義できます。


一番の基本 API Path.GetRelativePath(.NET Core 2.0+)

Path.GetRelativePath の使い方

.NET Core 2.0 以降(.NET 5/6/7/8 など)では、
System.IO.Path.GetRelativePath という、そのものズバリのメソッドが用意されています。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string basePath = @"C:\Apps\MyTool";
        string absolutePath = @"C:\Apps\MyTool\logs\app.log";

        string relative = Path.GetRelativePath(basePath, absolutePath);

        Console.WriteLine("基準ディレクトリ: " + basePath);
        Console.WriteLine("絶対パス        : " + absolutePath);
        Console.WriteLine("相対パス        : " + relative);
    }
}
C#

実行結果のイメージはこんな感じです。

  • 基準ディレクトリ: C:\Apps\MyTool
  • 絶対パス : C:\Apps\MyTool\logs\app.log
  • 相対パス : logs\app.log

ここでの重要ポイントは 2 つです。

  1. 第一引数が「基準ディレクトリ」
  2. 第二引数が「相対にしたい絶対パス」

順番を間違えると、まったく違う結果になるので注意してください。

.. を含む相対パスも自動で作ってくれる

基準ディレクトリと対象パスの位置関係によっては、.. を含む相対パスが生成されます。

string basePath = @"C:\Apps\MyTool\data";
string absolutePath = @"C:\Apps\MyTool\logs\app.log";

string relative = Path.GetRelativePath(basePath, absolutePath);

Console.WriteLine(relative);
C#

この場合の相対パスは、例えば ..\logs\app.log のようになります。

自分で ..\ を数えながら文字列を組み立てるのはバグの温床なので、
ここは素直に Path.GetRelativePath に任せるのが正解です。


実務で使える「絶対→相対」ユーティリティ

まずは素直なラッパーを作る

毎回 Path.GetRelativePath を直書きするのではなく、
「基準ディレクトリとパスを渡すと、必ず相対パスを返す」ユーティリティを用意しておくと、コードが読みやすくなります。

using System;
using System.IO;

public static class PathUtil
{
    public static string ToRelativePath(string baseDirectory, string path)
    {
        if (string.IsNullOrWhiteSpace(baseDirectory))
        {
            throw new ArgumentException("基準ディレクトリが空です。", nameof(baseDirectory));
        }

        if (string.IsNullOrWhiteSpace(path))
        {
            throw new ArgumentException("パスが空です。", nameof(path));
        }

        baseDirectory = Path.GetFullPath(baseDirectory);
        path = Path.GetFullPath(path);

        return Path.GetRelativePath(baseDirectory, path);
    }
}
C#

ここで深掘りしたいポイントは、

  • Path.GetFullPath両方を一度絶対パスに正規化してから 相対化している
  • これにより、「baseDirectory が相対パスで渡されても正しく動く」

というところです。

呼び出し側は、基準ディレクトリを相対で渡しても絶対で渡してもよく、
ユーティリティ側で「一旦絶対にそろえてから相対を計算する」ようにしておくと、バグを減らせます。

使い方の例(アプリケーションフォルダ基準)

アプリケーションのフォルダを基準にして、
ユーザーが選んだファイルのパスを「アプリ基準の相対パス」で保存したい、というケースを考えてみます。

class Program
{
    static void Main()
    {
        string appBase = AppContext.BaseDirectory;

        string selectedFile = @"C:\Apps\MyTool\data\input\2025\01\sales.csv";

        string relative = PathUtil.ToRelativePath(appBase, selectedFile);

        Console.WriteLine("アプリ基準の相対パス: " + relative);
    }
}
C#

例えば、結果が data\input\2025\01\sales.csv のようになれば、
設定ファイルにはこの相対パスだけを保存しておけばよくなります。

別の環境にデプロイしても、
「アプリフォルダの中の構造が同じなら、そのまま動く」
という状態を作れるわけです。


「相対で保存して、絶対で使う」往復のイメージ

保存時:絶対 → 相対

  1. ユーザーがファイルを選ぶ(絶対パス)
  2. アプリケーションフォルダ(または任意のルート)を基準に相対パスに変換
  3. 設定ファイルや DB に「相対パス」を保存
string appBase = AppContext.BaseDirectory;
string selectedFile = @"C:\Apps\MyTool\data\input\sales.csv";

string relative = PathUtil.ToRelativePath(appBase, selectedFile);
// relative を保存
C#

利用時:相対 → 絶対

  1. 設定ファイルから相対パスを読み込む
  2. 「相対 → 絶対」のユーティリティ(前回やった ToAbsolutePath)で絶対パスに戻す
  3. ファイルを開く・処理する
string appBase = AppContext.BaseDirectory;
string relative = "data\\input\\sales.csv";

string absolute = PathUtil.ToAbsolutePath(appBase, relative);
// absolute を使って File.Open など
C#

この「絶対 → 相対」「相対 → 絶対」の往復がきれいに設計できると、
パス周りのコードがかなりスッキリします。


ドライブが違う場合はどうなるか

C: と D: のようにドライブが違うケース

Path.GetRelativePath は、基本的に「同じルート(同じドライブ)」を前提にしています。
基準ディレクトリと対象パスのドライブが違う場合、
相対パスにできないので、ほぼ「そのまま絶対パスに近い形」が返ってきます。

例えば、

  • 基準: C:\Apps\MyTool
  • 対象: D:\Data\input.csv

のような場合です。

このケースでは、そもそも「相対パスで表現する意味があるか?」を考えたほうがよいです。
多くの場合、「別ドライブにあるものは相対ではなく絶対で扱う」という運用にします。

実務での割り切り方

ユーティリティ側で、例えばこんなポリシーを決めることもできます。

  • ドライブが同じなら相対パスを返す
  • ドライブが違うなら、絶対パスをそのまま返す(=相対化しない)

その場合は、Path.GetPathRoot などでルートを比較して分岐させます。


.NET Framework(Path.GetRelativePath がない環境)の場合

Uri を使った相対パス計算(古典的テクニック)

古い .NET Framework(4.x 系)には Path.GetRelativePath がありません。
その場合、System.Uri を使って相対パスを計算するのが定番のテクニックです。

using System;
using System.IO;

public static class PathUtilNetFx
{
    public static string ToRelativePath(string baseDirectory, string path)
    {
        if (string.IsNullOrWhiteSpace(baseDirectory))
        {
            throw new ArgumentException("基準ディレクトリが空です。", nameof(baseDirectory));
        }

        if (string.IsNullOrWhiteSpace(path))
        {
            throw new ArgumentException("パスが空です。", nameof(path));
        }

        baseDirectory = Path.GetFullPath(baseDirectory);
        path = Path.GetFullPath(path);

        if (!baseDirectory.EndsWith(Path.DirectorySeparatorChar.ToString()))
        {
            baseDirectory += Path.DirectorySeparatorChar;
        }

        var baseUri = new Uri(baseDirectory);
        var targetUri = new Uri(path);

        Uri relativeUri = baseUri.MakeRelativeUri(targetUri);

        string relativePath = Uri.UnescapeDataString(relativeUri.ToString());

        relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar);

        return relativePath;
    }
}
C#

ポイントは、

  • 両方を GetFullPath で絶対パスにしてから Uri に渡す
  • MakeRelativeUri で相対 URI を作る
  • / を OS の区切り文字(\)に置き換える

という流れです。

今から新しく書くなら、.NET 6/8 などで Path.GetRelativePath を使うのが素直ですが、
既存の .NET Framework プロジェクトをメンテするときには、このパターンを知っておくと役に立ちます。


例外とエラー処理を意識した絶対→相対変換

起こり得る問題

絶対 → 相対の変換自体は、Path.GetRelativePath が例外を投げることはあまり多くありませんが、
前段の GetFullPath で次のような問題が起こる可能性があります。

  • パスに不正な文字が含まれている
  • パスが OS の制限より長すぎる
  • ドライブ指定が不正

また、「基準ディレクトリが存在しない」こと自体は、相対計算には必ずしも致命的ではありませんが、
その後のファイル操作で確実に問題になります。

呼び出し側での例外処理の例

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string baseDir = AppContext.BaseDirectory;
        string absolute = @"C:\Apps\MyTool\data\input\sales.csv";

        try
        {
            string relative = PathUtil.ToRelativePath(baseDir, absolute);
            Console.WriteLine("相対パス: " + relative);
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine("引数エラー: " + ex.Message);
        }
        catch (PathTooLongException ex)
        {
            Console.WriteLine("パスが長すぎます: " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("想定外のエラー: " + ex.Message);
        }
    }
}
C#

実務では、「どの基準ディレクトリから、どのパスを相対化しようとして失敗したか」をログに残しておくと、
後から原因を追いやすくなります。


まとめ 実務で使える「絶対パス→相対パス」ユーティリティの考え方

絶対 → 相対の変換は、「環境に依存しないパス表現」を作るための大事なピースです。
ここをきちんと押さえておくと、設定ファイルやログ、ツールの使い勝手が一段レベルアップします。

押さえておきたいポイントは次のとおりです。

  • 相対パスには必ず「基準ディレクトリ」が必要
  • .NET 6/8 などでは Path.GetRelativePath(base, path) が素直で強力
  • 両方を一度 Path.GetFullPath で正規化してから相対化すると安定する
  • アプリケーションフォルダ(AppContext.BaseDirectory)や「データルート」を基準にする設計が実務では多い
  • 「保存時は絶対 → 相対」「利用時は相対 → 絶対」という往復をユーティリティで揃えておく
  • ドライブが違う場合は「相対にしない」という割り切りも設計としてアリ
  • 古い .NET Framework では Uri.MakeRelativeUri を使う方法も覚えておくと良い

ここまで押さえておけば、「パスが環境に縛られてつらい」「どこを指しているのか分からない」といった悩みはかなり減ります。

タイトルとURLをコピーしました