Path.Combine が Path.DirectorySeparatorChar で始まるファイル名を適切に連結しないのはなぜですか?

StackOverflow https://stackoverflow.com/questions/53102

  •  09-06-2019
  •  | 
  •  

質問

から イミディエイトウィンドウ Visual Studio の場合:

> Path.Combine(@"C:\x", "y")
"C:\\x\\y"
> Path.Combine(@"C:\x", @"\y")
"\\y"

両方とも同じである必要があるようです。

古い FileSystemObject.BuildPath() はこのようには機能しませんでした...

役に立ちましたか?

解決

Microsoft はドキュメントに記載されていることを正確に実行しているため、これは一種の哲学的な質問です (おそらく Microsoft だけが真に答えることができます)。

System.IO.Path.Combine

「path2 に絶対パスが含まれている場合、このメソッドは path2 を返します。」

実際の Combine メソッドは次のとおりです .NET ソースから。呼び出していることがわかります CombineNoChecks, 、次に呼び出します IsPathRooted path2 上にあり、存在する場合はそのパスを返します。

public static String Combine(String path1, String path2) {
    if (path1==null || path2==null)
        throw new ArgumentNullException((path1==null) ? "path1" : "path2");
    Contract.EndContractBlock();
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);

    return CombineNoChecks(path1, path2);
}

internal static string CombineNoChecks(string path1, string path2)
{
    if (path2.Length == 0)
        return path1;

    if (path1.Length == 0)
        return path2;

    if (IsPathRooted(path2))
        return path2;

    char ch = path1[path1.Length - 1];
    if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar &&
            ch != VolumeSeparatorChar) 
        return path1 + DirectorySeparatorCharAsString + path2;
    return path1 + path2;
}

どういう理屈なのか分かりません。解決策は、2 番目のパスの先頭から DirectorySeparatorChar を削除 (またはトリム) することだと思います。おそらく、それを実行してから Path.Combine() を呼び出す独自の Combine メソッドを作成してください。

他のヒント

これは逆アセンブルされたコードです .NETリフレクター Path.Combine メソッドの場合。IsPathRooted関数を確認してください。2 番目のパスがルート化されている (DirectorySeparatorChar で始まる) 場合は、2 番目のパスをそのまま返します。

public static string Combine(string path1, string path2)
{
    if ((path1 == null) || (path2 == null))
    {
        throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
    }
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);
    if (path2.Length == 0)
    {
        return path1;
    }
    if (path1.Length == 0)
    {
        return path2;
    }
    if (IsPathRooted(path2))
    {
        return path2;
    }
    char ch = path1[path1.Length - 1];
    if (((ch != DirectorySeparatorChar) &&
         (ch != AltDirectorySeparatorChar)) &&
         (ch != VolumeSeparatorChar))
    {
        return (path1 + DirectorySeparatorChar + path2);
    }
    return (path1 + path2);
}


public static bool IsPathRooted(string path)
{
    if (path != null)
    {
        CheckInvalidPathChars(path);
        int length = path.Length;
        if (
              (
                  (length >= 1) &&
                  (
                      (path[0] == DirectorySeparatorChar) ||
                      (path[0] == AltDirectorySeparatorChar)
                  )
              )

              ||

              ((length >= 2) &&
              (path[1] == VolumeSeparatorChar))
           )
        {
            return true;
        }
    }
    return false;
}

この問題を解決したかったのです:

string sample1 = "configuration/config.xml";
string sample2 = "/configuration/config.xml";
string sample3 = "\\configuration/config.xml";

string dir1 = "c:\\temp";
string dir2 = "c:\\temp\\";
string dir3 = "c:\\temp/";

string path1 = PathCombine(dir1, sample1);
string path2 = PathCombine(dir1, sample2);
string path3 = PathCombine(dir1, sample3);

string path4 = PathCombine(dir2, sample1);
string path5 = PathCombine(dir2, sample2);
string path6 = PathCombine(dir2, sample3);

string path7 = PathCombine(dir3, sample1);
string path8 = PathCombine(dir3, sample2);
string path9 = PathCombine(dir3, sample3);

もちろん、パス 1 ~ 9 のすべてには、最後に同等の文字列が含まれている必要があります。私が思いついたPathCombineメソッドは次のとおりです。

private string PathCombine(string path1, string path2)
{
    if (Path.IsPathRooted(path2))
    {
        path2 = path2.TrimStart(Path.DirectorySeparatorChar);
        path2 = path2.TrimStart(Path.AltDirectorySeparatorChar);
    }

    return Path.Combine(path1, path2);
}

また、この文字列処理を手動で行わなければならないのは非常に面倒だと思いますが、その背後にある理由に興味があります。

私の意見では、これはバグです。問題は、2 つの異なるタイプの「絶対」パスがあることです。パス「d:\mydir\myfile.txt」は絶対パスであり、パス「\mydir\myfile.txt」も、ドライブ文字が欠落している場合でも「絶対」とみなされます。私の意見では、正しい動作は、2 番目のパスがディレクトリ区切り文字で始まる (UNC パスではない) 場合に、最初のパスのドライブ文字を先頭に追加することです。必要に応じて、希望する動作を行う独自のヘルパー ラッパー関数を作成することをお勧めします。

から MSDN:

指定されたパスの 1 つが長さ 0 の文字列である場合、このメソッドはもう 1 つのパスを返します。path2 に絶対パスが含まれている場合、このメソッドは path2 を返します。

あなたの例では、path2 は絶対パスです。

続く クリスチャン・グラウス」というタイトルの彼の「Microsoft について嫌いな点」ブログでのアドバイスPath.Combine は本質的に役に立ちません。"、これが私の解決策です:

public static class Pathy
{
    public static string Combine(string path1, string path2)
    {
        if (path1 == null) return path2
        else if (path2 == null) return path1
        else return path1.Trim().TrimEnd(System.IO.Path.DirectorySeparatorChar)
           + System.IO.Path.DirectorySeparatorChar
           + path2.Trim().TrimStart(System.IO.Path.DirectorySeparatorChar);
    }

    public static string Combine(string path1, string path2, string path3)
    {
        return Combine(Combine(path1, path2), path3);
    }
}

名前空間は衝突するべきだとアドバイスする人もいます...一緒に行きました Pathy, 、軽微なものとして、との名前空間の衝突を避けるため System.IO.Path.

編集:nullパラメータチェックを追加しました

実際の詳細はわかりませんが、相対 URI に参加するのと同じように参加しようとしているのではないかと思います。例えば:

urljoin('/some/abs/path', '../other') = '/some/abs/other'

これは、前にスラッシュを付けてパスを結合すると、実際には 1 つのベースと別のベースを結合することになり、その場合は 2 番目のベースが優先されることを意味します。

このコードはうまくいくはずです:

        string strFinalPath = string.Empty;
        string normalizedFirstPath = Path1.TrimEnd(new char[] { '\\' });
        string normalizedSecondPath = Path2.TrimStart(new char[] { '\\' });
        strFinalPath =  Path.Combine(normalizedFirstPath, normalizedSecondPath);
        return strFinalPath;

理由:

2 番目の URL は絶対パスとみなされます。 Combine メソッドは、最後のパスが絶対パスの場合にのみ最後のパスを返します。

解決: 先頭のスラッシュを削除するだけです / 2 番目のパス (/SecondPathSecondPath)。その後、例外どおりに機能します。

これは、(相対) パスが通常どのように扱われるかを考えると、ある意味では理にかなっています。

string GetFullPath(string path)
{
     string baseDir = @"C:\Users\Foo.Bar";
     return Path.Combine(baseDir, path);
}

// Get full path for RELATIVE file path
GetFullPath("file.txt"); // = C:\Users\Foo.Bar\file.txt

// Get full path for ROOTED file path
GetFullPath(@"C:\Temp\file.txt"); // = C:\Temp\file.txt

本当の質問は次のとおりです。で始まるパスがなぜあるのか "\", 、「根付いている」と見なされますか?これも私にとっては初耳でしたが、 Windowsではそのように動作します:

new FileInfo("\windows"); // FullName = C:\Windows, Exists = True
new FileInfo("windows"); // FullName = C:\Users\Foo.Bar\Windows, Exists = False

これら 2 つの方法により、両方に区切り文字が含まれている 2 つの文字列を誤って結合することがなくなります。

    public static string Combine(string x, string y, char delimiter) {
        return $"{ x.TrimEnd(delimiter) }{ delimiter }{ y.TrimStart(delimiter) }";
    }

    public static string Combine(string[] xs, char delimiter) {
        if (xs.Length < 1) return string.Empty;
        if (xs.Length == 1) return xs[0];
        var x = Combine(xs[0], xs[1], delimiter);
        if (xs.Length == 2) return x;
        var ys = new List<string>();
        ys.Add(x);
        ys.AddRange(xs.Skip(2).ToList());
        return Combine(ys.ToArray(), delimiter);
    }

この\は「現在のドライブのルートディレクトリ」を意味します。あなたの例では、現在のドライブのルートディレクトリにある「test」フォルダーを意味します。したがって、これは「c: est」と等しくなります。

パスを失わずに両方のパスを結合したい場合は、これを使用できます。

?Path.Combine(@"C:\test", @"\test".Substring(0, 1) == @"\" ? @"\test".Substring(1, @"\test".Length - 1) : @"\test");

または変数を使用して:

string Path1 = @"C:\Test";
string Path2 = @"\test";
string FullPath = Path.Combine(Path1, Path2.Substring(0, 1) == @"\" ? Path2.Substring(1, Path2.Length - 1) : Path2);

どちらの場合も「C: est est」を返します。

まず、Path2 が / で始まるかどうかを評価し、それが true の場合は、最初の文字を除いて Path2 を返します。それ以外の場合は、完全な Path2 を返します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top