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


185

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

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

どちらも同じように見えるはずです。

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



@ジョー、バカは正しいです!また、同等の機能がNode.JSで正常に機能することを指摘する必要があります... Microsoftで頭を振る...
NH。

2
@zwcloud .NET Core / Standardの場合、Path.Combine()主に下位互換性のために(既存の動作と)。あなたはより良い使用してオフになるだろうPath.Join()。「、方法を組み合わせると違っ方法は、ルートに戻ったパスをしようとはしません(つまり、パス2が絶対パスである場合、組み合わせの方法は、廃棄パス1とリターンパス2ない参加しようメソッドはそうします。) "
Stajs

回答:


205

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

System.IO.Path.Combine

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

これが.NETソースからの実際のCombineメソッドです。あなたはそれが呼び出すことがわかります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;
}

根拠が何なのかわかりません。解決策は、2番目のパスの先頭からDirectorySeparatorCharを取り除く(またはトリムする)ことです。多分それを行う独自のCombineメソッドを記述してから、Path.Combine()を呼び出します。


逆アセンブルされたコードを見て(私の投稿を確認してください)、あなたはある意味で正しいです。
Gulzar Nazim

7
「現在動作しているディレクトリ」アルゴリズムに簡単にアクセスできるように、この方法で機能すると思います。
BCS

cd (component)コマンドラインからのシーケンスのように機能するようです。私には合理的に聞こえます。
エイドリアン・ラトナパラ2014

11
このトリムを使用して、目的のエフェクト文字列を取得します。strFilePath = Path.Combine(basePath、otherPath.TrimStart(new char [] {'\\'、 '/'}));
マシューロック

3
Path.Combine安全のために動作中のコードを変更しましたが、それが壊れてしまいました。非常に愚かです:)
sotn

23

これは、Path.Combineメソッド用の.NET Reflectorからの逆アセンブルされたコードです。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;
}

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

私の意見では、これはバグです。問題は、「絶対」パスには2つの異なるタイプがあることです。パス "d:\ mydir \ myfile.txt"は絶対パスであり、パス "\ mydir \ myfile.txt"もドライブ文字がない場合でも "絶対"と見なされます。私の意見では、正しい動作は、2番目のパスがディレクトリ区切り文字で始まる(UNCパスではない)ときに、最初のパスからドライブ文字を付加することです。私はあなたがそれを必要とするならあなたが望む振る舞いをするあなた自身のヘルパーラッパー関数を書くことを勧めます。


7
それは仕様と一致しますが、私が期待したものでもありません。
dthrasher

@Jakeそれはバグ修正を避けていません。それは何人かが何かをする方法について長くそして一生懸命考えて、そして彼らが同意するものに固執するということです。また、.Netフレームワーク(を含むライブラリPath.Combine)とC#言語の違いにも注意してください。
Grault

9

MSDNから:

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

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


7

クリスチャングラウス氏の「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パラメータチェックを追加


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'

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


スラッシュについて説明したほうがいいと思います。また、これは.NETとどのような関係がありますか?
Peter Mortensen

3

理由:

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

解決策:/ 2番目のパスの開始スラッシュ(/SecondPathto 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が/で始まるかどうかを評価し、それがtrueの場合、最初の文字なしでPath2を返します。それ以外の場合は、完全なPath2を返します。


1
考慮すべき唯一のキャラクターではないため、== @"\"チェックをPath.IsRooted()呼び出しで置き換える方がおそらく安全"\"です。
rumblefx0 2016年

0

これらの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);
    }

0

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



0

集約関数を使用して、パスを次のように結合しました。

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

0

ライアンによって言及されたように、それはドキュメントが言うことを正確に行っています。

DOS時代から、現在のディスクと現在のパスが区別されます。 \ルートパスですが、現在のディスク用です。

ディスク」ごとに個別の「現在のパス」があります。を使用してディスクを変更する場合はcd D:、現在のパスをに変更するのではなく、D:\「D:\ whatever \ was \ the \ last \ path \ accessed \ on \ this \ disk」...に変更します。

したがって、Windowsでは、リテラル@"\x"は「CURRENTDISK:\ x」を意味します。したがってPath.Combine(@"C:\x", @"\y")、2番目のパラメーターとして、既知のディスク内ではなく、相対パスではなくルートパスがあります...«現在のディスク»である可能性があることが不明であるため、pythonはを返します"\\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.