Tại sao Path.Combine không được ghép đúng tên tệp bắt đầu bằng Path.DirectorySeparatorChar?


185

Từ cửa sổ ngay lập tức trong Visual Studio:

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

Có vẻ như cả hai nên giống nhau.

FileSystemObject.BuildPath () cũ không hoạt động theo cách này ...



@Joe, ngu là đúng! Ngoài ra, tôi phải chỉ ra rằng chức năng tương đương chỉ hoạt động tốt trong Node.JS ... Lắc đầu tại Microsoft ...
NH.

2
@zwcloud Đối với .NET Core / Standard, Path.Combine()chủ yếu để tương thích ngược (với hành vi hiện có). Bạn nên sử dụng tốt hơn Path.Join(): "Không giống như phương thức Kết hợp, phương thức Tham gia không cố gắng root đường dẫn được trả về. (Nghĩa là, nếu path2 là một đường dẫn tuyệt đối, thì phương thức Tham gia không loại bỏ path1 và trả về path2 như Kết hợp phương thức nào.) "
Stajs

Câu trả lời:


205

Đây là một loại câu hỏi triết học (mà có lẽ chỉ Microsoft mới có thể thực sự trả lời), vì nó thực hiện chính xác những gì tài liệu nói.

System.IO.Path.Combine

"Nếu path2 chứa một đường dẫn tuyệt đối, phương thức này trả về path2."

Đây là phương thức Kết hợp thực tế từ nguồn .NET. Bạn có thể thấy rằng nó gọi CombineNoChecks , sau đó gọi IsPathRooted trên path2 và trả về đường dẫn đó nếu vậy:

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

Tôi không biết lý do là gì. Tôi đoán giải pháp là loại bỏ (hoặc Trim) DirectorySeparatorChar từ đầu đường dẫn thứ hai; có thể viết phương thức Kết hợp của riêng bạn để thực hiện điều đó và sau đó gọi Path.Combine ().


Nhìn vào mã được tháo rời (kiểm tra bài viết của tôi), bạn đã đúng.
Gulzar Nazim

7
Tôi đoán nó hoạt động theo cách đó để cho phép dễ dàng truy cập vào thuật toán "thư mục làm việc hiện tại".
BCS

Nó dường như hoạt động giống như thực hiện một chuỗi cd (component)từ dòng lệnh. Âm thanh hợp lý với tôi.
Adrian Ratnapala

11
Tôi sử dụng trang trí này để có được chuỗi hiệu ứng mong muốn strFilePath = Path.Combine (basePath, otherPath.TrimStart (new char [] {'\\', '/'}));
Khóa Matthew

3
Tôi đã thay đổi mã làm việc của mình thành Path.Combineđể an toàn nhưng sau đó nó bị
hỏng

23

Đây là mã được tách rời từ .NET Reflector cho phương thức Path.Combine. Kiểm tra chức năng IsPathRooted. Nếu đường dẫn thứ hai được root (bắt đầu với DirectorySeparatorChar), hãy trả về đường dẫn thứ hai như cũ.

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

23

Tôi muốn giải quyết vấn đề này:

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

Tất nhiên, cuối cùng tất cả các đường dẫn 1-9 đều chứa một chuỗi tương đương. Đây là phương pháp PathCombine mà tôi đã nghĩ ra:

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

Tôi cũng nghĩ rằng khá khó chịu khi việc xử lý chuỗi này phải được thực hiện thủ công và tôi quan tâm đến lý do đằng sau việc này.


19

Theo tôi đây là một lỗi. Vấn đề là có hai loại đường dẫn "tuyệt đối" khác nhau. Đường dẫn "d: \ mydir \ myfile.txt" là tuyệt đối, đường dẫn "\ mydir \ myfile.txt" cũng được coi là "tuyệt đối" mặc dù nó thiếu ký tự ổ đĩa. Theo tôi, hành vi đúng sẽ là thêm vào ký tự ổ đĩa từ đường dẫn thứ nhất khi đường dẫn thứ hai bắt đầu bằng dấu tách thư mục (và không phải là đường dẫn UNC). Tôi khuyên bạn nên viết chức năng trình bao bọc trợ giúp của riêng bạn có hành vi mà bạn mong muốn nếu bạn cần.


7
Nó phù hợp với thông số kỹ thuật, nhưng đó cũng không phải là điều tôi mong đợi.
dthrasher

@Jake Điều đó không tránh khỏi một lỗi; đó là một số người suy nghĩ lâu dài về cách làm một cái gì đó, và sau đó gắn bó với bất cứ điều gì họ đồng ý. Ngoài ra, lưu ý sự khác biệt giữa khung .Net (thư viện chứa Path.Combine) và ngôn ngữ C #.
Grault

9

Từ MSDN :

Nếu một trong các đường dẫn được chỉ định là một chuỗi có độ dài bằng không, phương thức này trả về đường dẫn khác. Nếu path2 chứa một đường dẫn tuyệt đối, phương thức này trả về path2.

Trong ví dụ của bạn, path2 là tuyệt đối.


7

Theo lời khuyên của Christian Graus trong blog "Những điều tôi ghét về Microsoft" có tiêu đề " Path.Combine về cơ bản là vô dụng. ", Đây là giải pháp của tôi:

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

Một số người khuyên rằng các không gian tên sẽ va chạm, ... tôi đã đi cùng Pathy, như một chút, và để tránh xung đột không gian tên với System.IO.Path.

Chỉnh sửa : Đã thêm kiểm tra tham số null


4

Mã này nên thực hiện thủ thuật:

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

4

Không biết chi tiết thực tế, tôi đoán là nó cố gắng tham gia như bạn có thể tham gia các URI tương đối. Ví dụ:

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

Điều này có nghĩa là khi bạn tham gia một đường dẫn với dấu gạch chéo trước, bạn thực sự đang nối một cơ sở này với một cơ sở khác, trong trường hợp thứ hai được ưu tiên.


Tôi nghĩ rằng các dấu gạch chéo về phía trước nên được giải thích. Ngoài ra, những gì có liên quan đến .NET?
Peter Mortensen

3

Lý do:

URL thứ hai của bạn được coi là một đường dẫn tuyệt đối, CombinePhương thức sẽ chỉ trả về đường dẫn cuối cùng nếu đường dẫn cuối cùng là một đường dẫn tuyệt đối.

Giải pháp: Chỉ cần xóa dấu gạch chéo bắt đầu /của Đường dẫn thứ hai ( /SecondPathđến SecondPath) của bạn. Sau đó, nó hoạt động như bạn ngoại trừ.


3

Điều này thực sự có ý nghĩa, theo một cách nào đó, xem xét cách các đường dẫn (tương đối) thường được xử lý:

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

Câu hỏi thực sự là: Tại sao các đường dẫn, bắt đầu bằng "\", được coi là "bắt nguồn"? Điều này cũng mới đối với tôi, nhưng nó hoạt động theo cách đó trên Windows :

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

1

Nếu bạn muốn kết hợp cả hai đường dẫn mà không mất bất kỳ đường dẫn nào, bạn có thể sử dụng đường dẫn này:

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

Hoặc với các biến:

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

Cả hai trường hợp đều trả về "C: \ test \ test".

Đầu tiên, tôi đánh giá nếu Path2 bắt đầu bằng / và nếu nó đúng, trả về Path2 mà không có ký tự đầu tiên. Nếu không, trả lại Path2 đầy đủ.


1
Có thể an toàn hơn để thay thế == @"\"kiểm tra bằng một Path.IsRooted()cuộc gọi vì "\"không phải là ký tự duy nhất để giải thích.
rumblefx0

0

Hai phương thức này sẽ cứu bạn khỏi việc vô tình nối hai chuỗi mà cả hai đều có dấu phân cách trong chúng.

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

0

Điều này có nghĩa là "thư mục gốc của ổ đĩa hiện tại". Trong ví dụ của bạn, nó có nghĩa là thư mục "test" trong thư mục gốc của ổ đĩa hiện tại. Vì vậy, điều này có thể bằng "c: \ test".


0

Xóa dấu gạch chéo bắt đầu ('\') trong tham số thứ hai (path2) của Path.Combine.


Câu hỏi không hỏi điều này.
LarsTech

0

Tôi đã sử dụng hàm tổng hợp để buộc các đường dẫn kết hợp như dưới đây:

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

0

Theo Ryan, nó đang làm chính xác những gì tài liệu nói.

Từ thời gian DOS, đĩa hiện tại và đường dẫn hiện tại được phân biệt. \là đường dẫn gốc, nhưng đối với DIUD HIỆN TẠI.

Đối với mỗi " đĩa ", có một " đường dẫn hiện tại " riêng biệt . Nếu bạn thay đổi đĩa bằng cách sử dụng, cd D:bạn không thay đổi đường dẫn hiện tại thànhD:\ , nhưng thành: "D: \ anything \ was \ the \ last \ path \ access \ on \ this \ đĩa" ...

Vì vậy, trong các cửa sổ, một nghĩa đen @"\x"có nghĩa là: "HIỆN TẠI: \ x". Do đó Path.Combine(@"C:\x", @"\y")có tham số thứ hai là đường dẫn gốc, không phải là họ hàng, mặc dù không có trong đĩa đã biết ... Và vì không biết đó có thể là «đĩa hiện tại», python trả về "\\y".

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.