Mengapa Path.Combine tidak menggabungkan nama file yang dimulai dengan Path.DirectorySeparatorChar?

185

Dari Jendela Segera di Visual Studio:

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

Tampaknya keduanya harus sama.

FileSystemObject.BuildPath () lama tidak berfungsi seperti ini ...

Kris Erickson
sumber
@ Jo, bodoh itu benar! Juga, saya harus menunjukkan bahwa fungsi setara berfungsi dengan baik di Node.JS ... Menggelengkan kepala di Microsoft ...
NH.
2
@ zwcloud Untuk .NET Core / Standard, Path.Combine()terutama untuk kompatibilitas mundur (dengan perilaku yang ada). Anda akan lebih baik menggunakan Path.Join(): "Tidak seperti metode Combine, metode Join tidak berusaha untuk me-root path yang dikembalikan. (Yaitu, jika path2 adalah path absolut, metode Join tidak membuang path1 dan mengembalikan path2 sebagai Combine metode melakukannya.) "
Stajs

Jawaban:

205

Ini semacam pertanyaan filosofis (yang barangkali hanya Microsoft yang benar-benar dapat menjawab), karena ia melakukan persis seperti yang dikatakan dokumentasi.

System.IO.Path.Combine

"Jika path2 berisi path absolut, metode ini mengembalikan path2."

Inilah metode Combine aktual dari sumber .NET. Anda dapat melihat bahwa ia memanggil CombineNoChecks , yang kemudian memanggil IsPathRooted di path2 dan mengembalikan jalur itu jika demikian:

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;
}

Saya tidak tahu apa alasannya. Saya kira solusinya adalah menghapus (atau Trim) DirectorySeparatorChar dari awal jalur kedua; mungkin menulis metode Combine Anda sendiri yang melakukan itu dan kemudian memanggil Path.Combine ().

Ryan Lundy
sumber
Melihat kode yang dibongkar (lihat posting saya), Anda benar.
Gulzar Nazim
7
Saya kira itu berfungsi seperti itu untuk memungkinkan akses mudah ke algoritma "saat ini bekerja".
BCS
Tampaknya berfungsi seperti melakukan urutan dari cd (component)baris perintah. Kedengarannya masuk akal bagi saya.
Adrian Ratnapala
11
Saya menggunakan trim ini untuk mendapatkan efek string yang diinginkan strFilePath = Path.Combine (basePath, otherPath.TrimStart (char baru [] {'\\', '/'}));
Matthew Lock
3
Saya memang mengubah kode kerja saya menjadi Path.Combinehanya untuk aman tetapi kemudian rusak .. Ini sangat bodoh :)
sotn
23

Ini adalah kode yang dibongkar dari .NET Reflector untuk metode Path.Combine. Periksa fungsi IsPathRooted. Jika path kedua di-root (dimulai dengan DirectorySeparatorChar), kembalikan path kedua apa adanya.

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;
}
Gulzar Nazim
sumber
23

Saya ingin menyelesaikan masalah ini:

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);

Tentu saja, semua jalur 1-9 harus berisi string yang setara pada akhirnya. Inilah metode PathCombine yang saya buat:

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);
}

Saya juga berpikir bahwa cukup menjengkelkan bahwa penanganan string ini harus dilakukan secara manual, dan saya akan tertarik dengan alasan di balik ini.

anhoppe
sumber
19

Menurut saya ini adalah bug. Masalahnya adalah bahwa ada dua jenis jalur "absolut" yang berbeda. Path "d: \ mydir \ myfile.txt" adalah absolut, path "\ mydir \ myfile.txt" juga dianggap "absolut" meskipun tidak ada huruf drive. Perilaku yang benar, menurut saya, adalah dengan menambahkan huruf drive dari lintasan pertama ketika lintasan kedua dimulai dengan pemisah direktori (dan bukan lintasan UNC). Saya akan merekomendasikan untuk menulis fungsi helper wrapper Anda sendiri yang memiliki perilaku yang Anda inginkan jika Anda membutuhkannya.

Baji
sumber
7
Ini cocok dengan spek, tapi itu juga tidak seperti yang saya harapkan.
dthrasher
@ Jake Itu tidak menghindari perbaikan bug; itu beberapa orang berpikir panjang dan keras tentang bagaimana melakukan sesuatu, dan kemudian berpegang teguh pada apa pun yang mereka sepakati. Perhatikan juga perbedaan antara kerangka .Net (pustaka yang berisi Path.Combine) dan bahasa C #.
Grault
9

Dari MSDN :

Jika salah satu jalur yang ditentukan adalah string dengan panjang nol, metode ini mengembalikan jalur lainnya. Jika path2 berisi path absolut, metode ini mengembalikan path2.

Dalam contoh Anda, path2 mutlak.

nickd
sumber
7

Mengikuti saran Christian Graus dalam blog "Things I Hate about Microsoft" yang berjudul " Path.Combine pada dasarnya tidak berguna. ", Berikut adalah solusi saya:

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);
    }
}

Beberapa menyarankan bahwa namespace harus bertabrakan, ... Saya pergi dengan Pathy, sebagai sedikit, dan untuk menghindari tabrakan namespace dengan System.IO.Path.

Sunting : Menambahkan pemeriksaan parameter nol

ergohack
sumber
4

Kode ini harus melakukan trik:

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

Tidak mengetahui detail sebenarnya, tebakan saya adalah berusaha membuat bergabung seperti Anda mungkin bergabung dengan URI relatif. Sebagai contoh:

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

Ini berarti bahwa ketika Anda bergabung dengan jalur dengan garis miring sebelumnya, Anda sebenarnya bergabung dengan satu pangkalan dengan yang lain, dalam hal ini yang kedua mendapat prioritas.

elarson
sumber
Saya pikir garis miring ke depan harus dijelaskan. Juga, apa hubungannya dengan .NET?
Peter Mortensen
3

Alasan:

URL kedua Anda dianggap sebagai jalur absolut, CombineMetode ini hanya akan mengembalikan jalur terakhir jika jalur terakhir adalah jalur absolut.

Solusi: Hapus saja garis miring awal /Path kedua Anda ( /SecondPathhinggaSecondPath ). Maka itu berfungsi seperti yang Anda perkecualian.

Amir Hossein Ahmadi
sumber
3

Ini sebenarnya masuk akal, dalam beberapa cara, mengingat bagaimana (relatif) jalur diperlakukan biasanya:

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

Pertanyaan sebenarnya adalah: Mengapa path, yang dimulai dengan "\", dianggap "rooted"? Ini juga baru bagi saya, tetapi berfungsi seperti itu di Windows :

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

Jika Anda ingin menggabungkan kedua jalur tanpa kehilangan jalur apa pun, Anda dapat menggunakan ini:

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

Atau dengan variabel:

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

Kedua kasus mengembalikan "C: \ test \ test".

Pertama, saya mengevaluasi apakah Path2 dimulai dengan / dan jika itu benar, kembalikan Path2 tanpa karakter pertama. Jika tidak, kembalikan Path2 lengkap.

Ferri
sumber
1
Mungkin lebih aman untuk mengganti == @"\"cek dengan Path.IsRooted()panggilan karena "\"bukan satu-satunya karakter yang perlu diperhitungkan.
rumblefx0
0

Kedua metode ini akan menyelamatkan Anda dari bergabung secara tidak sengaja dua string yang keduanya memiliki pembatas di dalamnya.

    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);
    }
Don Rolling
sumber
0

Ini berarti "direktori root drive saat ini". Dalam contoh Anda ini berarti folder "test" di direktori root drive saat ini. Jadi, ini bisa sama dengan "c: \ test".

Estevez
sumber
0

Hapus slash awal ('\') di parameter kedua (path2) Path.Combine.

shanmuga raja
sumber
Pertanyaannya bukan menanyakan ini.
LarsTech
0

Saya menggunakan fungsi agregat untuk memaksa jalur menggabungkan seperti di bawah ini:

public class MyPath    
{
    public static string ForceCombine(params string[] paths)
    {
        return paths.Aggregate((x, y) => Path.Combine(x, y.TrimStart('\\')));
    }
}
Laz Ziya
sumber
0

Seperti disebutkan oleh Ryan itu melakukan persis apa yang dikatakan dokumentasi.

Dari waktu DOS, disk saat ini, dan jalur saat ini dibedakan. \adalah path root, tetapi untuk CURRENT DISK.

Untuk setiap " disk " ada " jalur saat ini " yang terpisah . Jika Anda mengubah disk menggunakan cd D:Anda tidak mengubah jalur saat ini untuk D:\, tetapi untuk: "D: \ apa pun \ \ \ terakhir \ path \ diakses \ pada \ ini \ disk" ...

Jadi, di windows, literal @"\x"berarti: "CURRENTDISK: \ x". Oleh karena itu Path.Combine(@"C:\x", @"\y")memiliki parameter kedua path root, bukan relatif, meskipun tidak dalam disk yang dikenal ... Dan karena tidak diketahui yang mungkin menjadi «disk saat ini», python kembali "\\y".

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
ilias iliadis
sumber