Por que Caminho.Combinar corretamente não concatenar nomes de arquivos que começam com o Caminho.DirectorySeparatorChar?
Pergunta
Do Janela Imediata no Visual Studio:
> Path.Combine(@"C:\x", "y")
"C:\\x\\y"
> Path.Combine(@"C:\x", @"\y")
"\\y"
Parece que ambos devem ser o mesmo.
O velho FileSystemObject.BuildPath() não funciona dessa maneira...
Solução
Esta é uma questão filosófica (o que, talvez, só a Microsoft pode realmente responder), uma vez que ele está fazendo exatamente o que diz a documentação.
Do sistema.IO.Caminho.Combinar
"Se caminho2 contém um caminho absoluto, este método retorna path2."
Aqui está o real Combinar o método a partir do .Fonte LÍQUIDA.Você pode ver que ele chama CombineNoChecks, que chama IsPathRooted no caminho2 e retorna esse caminho, se assim:
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;
}
Eu não sei o que o raciocínio é.Eu acho que a solução é tira (ou Guarnição) DirectorySeparatorChar a partir do início do segundo caminho;talvez escrever o seu próprio Combinar o método que faz isso e, em seguida, chama Caminho.Combinar().
Outras dicas
Este é desmontado código de .NET Refletor para o Caminho.Combinar o método.Verifique IsPathRooted função.Se o segundo caminho está enraizada (começa com um DirectorySeparatorChar), retornar segundo caminho como ele é.
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;
}
Eu queria resolver esse problema:
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);
Claro, todos os caminhos 1-9 deve conter um equivalente de seqüência de caracteres no final.Aqui é o PathCombine método que eu vim com:
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);
}
Eu também acho que é muito chato que esta manipulação de cadeias de caracteres tem de ser feito manualmente, e eu estaria interessado em que a razão por trás disso.
Na minha opinião isso é um erro.O problema é que existem dois tipos diferentes de "absoluto" caminhos.O caminho "d:\mydir\myfile.txt" é absoluta, o caminho "\mydir\myfile.txt" também é considerado para ser "absoluto" mesmo que está faltando a letra da unidade.O comportamento correto, na minha opinião, seria a de colocar a letra da drive a partir do primeiro caminho quando o segundo caminho começa com o separador de diretório (e não é um caminho UNC).Eu recomendaria a escrever o seu próprio auxiliar função de wrapper que tem o comportamento que você desejar, se você precisar dele.
A partir de MSDN:
Se um dos caminhos especificados é uma cadeia de comprimento zero, este método retorna o caminho.Se caminho2 contém um caminho absoluto, este método retorna path2.
No seu exemplo, path2 é absoluto.
Seguinte Cristão Graus o conselho, em sua "Coisas que eu Odeio em sobre o Microsoft" blog intitulado "Caminho.Combinar é essencialmente inútil."aqui está a minha solução:
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);
}
}
Alguns aconselham que os espaços para nome devem colidir, ...Eu fui com Pathy
, como uma ligeira, e para evitar a colisão de espaço de nomes com System.IO.Path
.
Editar:Adicionado parâmetro null verifica
Não sabendo o real detalhes, o meu palpite é que ele faz uma tentativa de ingressar como você pode ingressar em relação URIs.Por exemplo:
urljoin('/some/abs/path', '../other') = '/some/abs/other'
Isso significa que quando você participar de um caminho com um precedente de barra, na verdade, você está ingressando em uma base para outra, caso em que o segundo fica precedência.
Este código deve fazer o truque:
string strFinalPath = string.Empty;
string normalizedFirstPath = Path1.TrimEnd(new char[] { '\\' });
string normalizedSecondPath = Path2.TrimStart(new char[] { '\\' });
strFinalPath = Path.Combine(normalizedFirstPath, normalizedSecondPath);
return strFinalPath;
Motivo:
O segundo URL é considerado um caminho absoluto, O Combine
método irá retornar apenas o último caminho, se o último caminho é um caminho absoluto.
Solução: Basta remover a barra de partida /
segundo de seu Caminho (/SecondPath
para SecondPath
).Em seguida, ele funciona como exceção.
Isso realmente faz sentido, de alguma forma, considerando-se como (relativo) caminhos são tratados normalmente:
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
A questão real é:Por que caminhos, que começam com "\"
, considerado "de raiz"?Isso era novo para mim também, mas funciona no Windows:
new FileInfo("\windows"); // FullName = C:\Windows, Exists = True
new FileInfo("windows"); // FullName = C:\Users\Foo.Bar\Windows, Exists = False
Estes dois métodos devem salvar acidentalmente unir duas cordas que ambos têm o delimitador-los.
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);
}
Este \ significa "o diretório raiz da unidade atual".No seu exemplo, significa a pasta "test" no atual diretório raiz da unidade.Assim, este pode ser igual a "c: est".
Se você deseja combinar ambos os caminhos, sem perder qualquer caminho que você pode usar isto:
?Path.Combine(@"C:\test", @"\test".Substring(0, 1) == @"\" ? @"\test".Substring(1, @"\test".Length - 1) : @"\test");
Ou com as variáveis:
string Path1 = @"C:\Test";
string Path2 = @"\test";
string FullPath = Path.Combine(Path1, Path2.Substring(0, 1) == @"\" ? Path2.Substring(1, Path2.Length - 1) : Path2);
Ambos os casos o retorno "C: est est".
Primeiro, eu avaliar se Caminho2 começa com / e se for verdadeiro, volte Caminho2 sem o primeiro caractere.Caso contrário, retorne todo o Path2.