เหตุใด Path.Combine ไม่เชื่อมต่อชื่อไฟล์ที่ขึ้นต้นด้วย Path.DirectorySeparatorChar อย่างถูกต้อง


186

จากหน้าต่างทันทีใน Visual Studio:

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

ดูเหมือนว่าพวกเขาทั้งสองควรจะเหมือนกัน

FileSystemObject.BuildPath () เก่าไม่ทำงานอย่างนี้ ...



@ โจโง่อย่างถูกต้อง! นอกจากนี้ฉันต้องชี้ให้เห็นว่าฟังก์ชั่นเทียบเท่าทำงานได้ดีในโหนด JS ... เขย่าหัวของฉันที่ Microsoft ...
NH

2
@zwcloud สำหรับ. NET Core / Standard Path.Combine()ส่วนใหญ่ใช้สำหรับความเข้ากันได้แบบย้อนกลับ (พร้อมกับพฤติกรรมที่มีอยู่) คุณน่าจะดีกว่าการใช้Path.Join(): "ซึ่งแตกต่างจากวิธีการรวมวิธีการเข้าร่วมไม่พยายามรูทพา ธ ที่ส่งคืน (นั่นคือถ้า path2 เป็นพา ธ สัมบูรณ์ วิธีการทำ) "
Stajs

คำตอบ:


205

นี่เป็นคำถามเชิงปรัชญา (ซึ่งอาจเป็นเพียง Microsoft เท่านั้นที่สามารถตอบได้อย่างแท้จริง) เนื่องจากเป็นสิ่งที่ทำตามที่เอกสารระบุไว้

System.IO.Path.Combine

"ถ้า path2 มีพา ธ สัมบูรณ์วิธีนี้จะคืนค่า path2"

นี่คือวิธีการรวมที่เกิดขึ้นจริงจากแหล่ง. 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;
}

ฉันไม่รู้ว่าเหตุผลคืออะไร ฉันเดาทางออกคือการแยก (หรือตัด) DirectorySeparatorChar จากจุดเริ่มต้นของเส้นทางที่สอง; อาจเขียนวิธีการรวมของคุณเองที่ทำอย่างนั้นแล้วเรียก Path.Combine ()


มองไปที่รหัสถอดประกอบ (ตรวจสอบโพสต์ของฉัน) คุณมีสิทธิในทาง
Gulzar Nazim

7
ฉันเดาว่ามันจะทำงานแบบนั้นเพื่อให้เข้าถึงอัลกอริทึม "ปัจจุบันทำงาน dir"
BCS

ดูเหมือนว่าจะทำงานเหมือนทำลำดับของcd (component)จากบรรทัดคำสั่ง ฟังดูสมเหตุสมผลสำหรับฉัน
Adrian Ratnapala

11
ฉันใช้การตัดแต่งนี้เพื่อรับเอฟเฟกต์สตริงที่ต้องการ strFilePath = Path.Combine (basePath, otherPath.TrimStart (อักขระใหม่ [] {'\\', '/'}));
Matthew Lock

3
ผมไม่เปลี่ยนรหัสการทำงานของฉันเป็นPath.Combineเพียงเพื่อความปลอดภัย แต่แล้วมันยากจน .. มันโง่ดังนั้น :)
sotn

23

นี่คือรหัสถอดประกอบจาก. NET สะท้อนแสงสำหรับวิธี Path.Combine ตรวจสอบฟังก์ชัน IsPathRooted หากพา ธ ที่สองถูกรูท (เริ่มต้นด้วย DirectorySeparatorChar) ให้ส่งคืนพา ธ ที่สองตามที่เป็นอยู่

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

ฉันต้องการที่จะแก้ปัญหานี้:

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

ฉันยังคิดว่ามันค่อนข้างน่ารำคาญที่การจัดการสตริงจะต้องทำด้วยตนเองและฉันสนใจเหตุผลที่อยู่เบื้องหลัง


19

ในความคิดของฉันนี่เป็นข้อผิดพลาด ปัญหาคือมีเส้นทาง"สัมบูรณ์ "สองประเภทที่แตกต่างกัน เส้นทาง "d: \ mydir \ myfile.txt" เป็นแบบสัมบูรณ์เส้นทาง "\ mydir \ myfile.txt" ก็ถือว่าเป็น "แน่นอน" แม้ว่าจะไม่มีตัวอักษรไดรฟ์ ในความคิดของฉันพฤติกรรมที่ถูกต้องคือการเพิ่มอักษรระบุไดรฟ์จากเส้นทางแรกเมื่อเส้นทางที่สองเริ่มต้นด้วยตัวคั่นไดเรกทอรี (และไม่ใช่เส้นทาง UNC) ฉันอยากจะแนะนำให้เขียนฟังก์ชั่น wrapper helper ของคุณเองซึ่งมีพฤติกรรมที่คุณต้องการหากคุณต้องการ


7
มันตรงกับสเป็ค แต่ไม่ใช่สิ่งที่ฉันคาดหวัง
dthrasher

@ เจคนั่นคือไม่หลีกเลี่ยงการแก้ไขข้อผิดพลาด; นั่นเป็นหลายคนที่คิดมานานและหนักหนาว่าจะทำอะไรแล้วทำตามที่เห็นด้วย นอกจากนี้ให้สังเกตความแตกต่างระหว่าง. Net framework ( Path.Combineไลบรารี่ที่มี) และภาษา C #
Grault

9

จากMSDN :

หากหนึ่งในเส้นทางที่ระบุเป็นสตริงที่มีความยาวเป็นศูนย์วิธีนี้จะส่งกลับเส้นทางอื่น ถ้า path2 มีพา ธ สัมบูรณ์วิธีนี้จะส่งคืน path2

ในตัวอย่างของคุณ path2 เป็นค่าสัมบูรณ์


7

ทำตามคำแนะนำของChristian Grausในบล็อก "สิ่งที่ฉันเกลียดเกี่ยวกับ 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);
    }
}

ให้คำแนะนำบางอย่างที่ควร namespaces ชนกัน ... ฉันไปกับเป็นเล็กน้อยและเพื่อหลีกเลี่ยงการปะทะกับPathy namespaceSystem.IO.Path

แก้ไข : เพิ่มการตรวจสอบพารามิเตอร์ว่าง


4

รหัสนี้ควรทำเคล็ดลับ:

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

4

ไม่ทราบรายละเอียดที่แท้จริงฉันเดาว่าจะพยายามเข้าร่วมเหมือนคุณอาจเข้าร่วม URI ที่เกี่ยวข้อง ตัวอย่างเช่น:

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

ซึ่งหมายความว่าเมื่อคุณเข้าร่วมเส้นทางที่มีเครื่องหมายทับก่อนหน้าคุณกำลังเข้าร่วมฐานหนึ่งไปยังอีกฐานหนึ่งซึ่งในกรณีที่สองได้รับความสำคัญ


ฉันคิดว่าเครื่องหมายทับซ้ายควรได้รับการอธิบาย นอกจากนี้สิ่งนี้เกี่ยวกับ. NET
Peter Mortensen

3

เหตุผล:

URL ที่สองของคุณถือเป็นเส้นทางแบบสัมบูรณ์ Combineวิธีนี้จะกลับเส้นทางสุดท้ายหากเส้นทางสุดท้ายเป็นเส้นทางที่แน่นอน

การแก้ไข:เพียงลบเครื่องหมายสแลชเริ่มต้น/ของเส้นทางที่สองของคุณ ( /SecondPathไปSecondPath) จากนั้นก็ทำงานได้ตามที่คุณยกเว้น


3

เรื่องนี้ทำให้รู้สึกจริง ๆ ในทางใดทางหนึ่งพิจารณาว่าเส้นทาง (ญาติ) ได้รับการปฏิบัติตามปกติ:

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

1

หากคุณต้องการรวมทั้งสองเส้นทางโดยไม่เสียเส้นทางใด ๆ คุณสามารถใช้สิ่งนี้:

?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.IsRooted() ? Path2.Substring(1, Path2.Length - 1) : Path2);

ทั้งสองกรณีส่งคืน "C: \ test \ test"

ก่อนอื่นฉันประเมินว่า Path2 เริ่มต้นด้วย / และถ้าเป็นจริงให้ส่งคืน Path2 โดยไม่มีอักขระตัวแรก มิฉะนั้นส่งคืน Path2 แบบเต็ม


1
มันอาจปลอดภัยกว่าในการแทนที่== @"\"เช็คด้วยการPath.IsRooted()โทรเนื่องจาก"\"ไม่ใช่อักขระตัวเดียวที่ใช้แทน
rumblefx0

0

ทั้งสองวิธีนี้จะช่วยให้คุณประหยัดจากการเข้าร่วมสองสายที่ทั้งสองมีตัวคั่นอยู่โดยไม่ตั้งใจ

    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

นี่หมายถึง "ไดเรกทอรีรากของไดรฟ์ปัจจุบัน" ในตัวอย่างของคุณหมายถึงโฟลเดอร์ "test" ในไดเรกทอรีรากของไดรฟ์ปัจจุบัน ดังนั้นนี่อาจเท่ากับ "c: \ test"


0

ลบเครื่องหมายสแลชเริ่มต้น ('\') ในพารามิเตอร์ที่สอง (path2) ของ Path.Combine


คำถามไม่ได้ถามคำถามนี้
LarsTech

0

ฉันใช้ฟังก์ชั่นรวมเพื่อบังคับให้เส้นทางรวมกันดังนี้:

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

0

ตามที่กล่าวถึงโดย Ryan มันกำลังทำสิ่งที่เอกสารระบุไว้

จากเวลา DOS ดิสก์ปัจจุบันและเส้นทางปัจจุบันจะแตกต่าง \เป็นรูทพา ธ แต่สำหรับดิสก์หมุนเวียน

ทุก ๆ " ดิสก์ " มีแยก " เส้นทางปัจจุบัน " หากคุณเปลี่ยนดิสก์โดยใช้cd D:คุณจะไม่เปลี่ยนพา ธ ปัจจุบันเป็นD:\แต่เป็น: "D: \ what \ was \ the \ last \ path \ access \ access \ on \ this \ disk" ...

ดังนั้นใน Windows หมาย@"\x"ถึงตัวอักษร: "CURRENTDISK: \ x" ดังนั้นจึงPath.Combine(@"C:\x", @"\y")มีพารามิเตอร์ที่สองเป็นรูทพา ธ ไม่ใช่แบบสัมพันธ์แม้ว่าจะไม่ได้อยู่ในดิสก์ที่รู้จัก ... และเนื่องจากไม่ทราบว่าอาจเป็น«ดิสก์ปัจจุบัน»จึงส่งกลับไพ"\\y"ธ อน

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.