لماذا لا يقوم Path.Combine بتسلسل أسماء الملفات التي تبدأ بـ Path.DirectorySeparatorChar بشكل صحيح؟

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

  •  09-06-2019
  •  | 
  •  

سؤال

من نافذة فورية في فيجوال ستوديو:

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

ويبدو أنهما يجب أن يكونا متماثلين.

FileSystemObject.BuildPath() القديم لم يعمل بهذه الطريقة...

هل كانت مفيدة؟

المحلول

هذا نوع من السؤال الفلسفي (الذي ربما تكون شركة Microsoft وحدها هي التي تستطيع الإجابة عليه حقًا)، نظرًا لأنها تفعل بالضبط ما تقوله الوثائق.

System.IO.Path.Combine

"إذا كان المسار2 يحتوي على مسار مطلق، فإن هذه الطريقة ترجع المسار2."

إليك طريقة الجمع الفعلية من مصدر .NET.يمكنك أن ترى أنه يدعو CombineNoChecks, ، والذي يدعو بعد ذلك IsPathRooted على المسار2 وإرجاع هذا المسار إذا كان الأمر كذلك:

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 من بداية المسار الثاني؛ربما تكتب طريقة Combine الخاصة بك والتي تقوم بذلك ثم تستدعي Path.Combine().

نصائح أخرى

هذا هو الكود المفكك من صافي العاكس لأسلوب 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;
}

أردت حل هذه المشكلة:

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

أعتقد أيضًا أنه من المزعج جدًا أن تتم معالجة السلسلة يدويًا، وسأكون مهتمًا بمعرفة السبب وراء ذلك.

في رأيي هذا خطأ.المشكلة هي أن هناك نوعين مختلفين من المسارات "المطلقة".المسار "d:\mydir\myfile.txt" مطلق، ويعتبر المسار "\mydir\myfile.txt" أيضًا "مطلقًا" على الرغم من أنه يفتقد حرف محرك الأقراص.السلوك الصحيح، في رأيي، هو إضافة حرف محرك الأقراص من المسار الأول إلى المقدمة عندما يبدأ المسار الثاني بفاصل الدليل (وليس مسار UNC).أوصي بكتابة وظيفة المجمع المساعد الخاصة بك والتي تحتوي على السلوك الذي تريده إذا كنت في حاجة إليه.

من MSDN:

إذا كان أحد المسارات المحددة عبارة عن سلسلة ذات طول صفري، تقوم هذه الطريقة بإرجاع المسار الآخر.إذا كان المسار2 يحتوي على مسار مطلق، فستُرجع هذه الطريقة المسار2.

في المثال الخاص بك، 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.

يحرر:تمت إضافة عمليات فحص المعلمات الفارغة

لا أعرف التفاصيل الفعلية، أعتقد أنه يحاول الانضمام مثلما قد تنضم إلى عناوين URI النسبية.على سبيل المثال:

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

هذا يعني أنه عند ضم مسار بشرطة مائلة سابقة، فإنك في الواقع تربط قاعدة بأخرى، وفي هذه الحالة تكون للقاعدة الثانية الأسبقية.

يجب أن يقوم هذا الرمز بالخدعة:

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

سبب:

يعتبر عنوان URL الثاني الخاص بك مسارًا مطلقًا، The Combine لن تقوم الطريقة بإرجاع المسار الأخير إلا إذا كان المسار الأخير مسارًا مطلقًا.

حل: ما عليك سوى إزالة شرطة البداية المائلة / طريقك الثاني(/SecondPath ل SecondPath).ثم يعمل كما كنت مستثناة.

وهذا أمر منطقي في الواقع، بطريقة ما، مع الأخذ في الاعتبار كيفية التعامل مع المسارات (النسبية) عادةً:

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

من المفترض أن تحميك هاتان الطريقتان من الانضمام عن طريق الخطأ إلى سلسلتين يحتوي كل منهما على المحدد.

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

هذا \ يعني "الدليل الجذر لمحرك الأقراص الحالي".في المثال الخاص بك، يعني ذلك مجلد "الاختبار" الموجود في الدليل الجذر لمحرك الأقراص الحالي.لذا، يمكن أن يكون هذا مساويًا لـ "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 يبدأ بـ / وإذا كان صحيحًا، فأرجع Path2 بدون الحرف الأول.وإلا، قم بإرجاع Path2 الكامل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top