So as I am building a Folder/File checking conditional, and a co-worker says it is "better" to use Path.Combine:
string finalPath = Path.Combine(folder, "file.txt");
as opposed to the way I was doing it with
string finalPath = folder + "\\file.txt";
Any sound reasoning this is "better?"
It's an interesting question;
You could, of course, write something like:
string finalPath = String.Format("{0}\\file.txt", folder);
To achieve the result you want.
Using ILSpy, though, lets see why Path.Combine is better.
The overload you are calling is:
public static string Combine(string path1, string path2)
{
if (path1 == null || path2 == null)
{
throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
}
Path.CheckInvalidPathChars(path1, false);
Path.CheckInvalidPathChars(path2, false);
return Path.CombineNoChecks(path1, path2);
}
The advantages are obvious; firstly, the function checks for null values and throws the appropriate exception. Then it checks for illegal characters in either of the arguments, and throws an appropriate exception. Once it is satisfied, it calls Path.CombineNoChecks:
private static string CombineNoChecks(string path1, string path2)
{
if (path2.Length == 0)
{
return path1;
}
if (path1.Length == 0)
{
return path2;
}
if (Path.IsPathRooted(path2))
{
return path2;
}
char c = path1[path1.Length - 1];
if (c != Path.DirectorySeparatorChar && c != Path.AltDirectorySeparatorChar && c != Path.VolumeSeparatorChar)
{
return path1 + Path.DirectorySeparatorChar + path2;
}
return path1 + path2;
}
The most interesting thing here are the characters it supports;
Path.DirectorySeparatorChar = "\\"
Path.AltDirectorySeparatorChar = "/"
Path.VolumeSeparatorChar = ":"
So it will also support paths where the separator is the wrong way around (like from a urn file://C:/blah, too)
In short, it's better because it gives you validation, a degree of portability (the 3 constants shown above can be defined on a per framework-OS basis), and has support for more than one type of path that you commonly encounter.
try these two to see the difference.... It can handle URI and standard paths. So always use Path.Combine.
Console.WriteLine(Path.Combine(#"file:///c:/temp/", "x.xml"));
Output file:///c:/temp/x.xml
Console.WriteLine(Path.Combine(#"C:\test", "x.xml"));
Output C:\test\x.xml
Yes, it's more portable in the case that the file-path separator is different to \
First you can use this notation #"\file.txt instead of "\\file.txt";
Second, let .Net care about fixing the path. There is a reason we have it.
You can be 100% sure that you've done it correctly but if you start combining paths by hand everywhere in your code, there is always a chance to create bugs.
A simple example.
User enters a path and you want to create a subfolder named temp there. What will you do?
If no backslash at the end, add one, else do this, otherwise do the other... etc.
With Path.Combine() you don't have to do checking. You can concentrate on the actual logic of your application.
One really could thing plus the other comments, it is the capability to combine many parts of the directory you want to create.
As an example is this:
Path.Combine(root, nextFolder, childfolder, file);
It supports many characters as it receives an array of string, so it is capable to create the right directory in one single executed line.
Regards,
Related
I'm curious what exactly the behavior is on the following:
FileInfo info = new FileInfo("C:/testfile.txt.gz");
string ext = info.Extension;
Will this return ".txt.gz" or ".gz"?
What is the behavior with even more extensions, such as ".txt.gz.zip" or something like that?
EDIT:
To be clear, I've already tested this. I would like an explanation of the property.
It will return .gz, but the explanation from MSDN (FileSystemInfo.Extension Property) isn't clear why:
"The Extension property returns the FileSystemInfo extension, including the period (.). For example, for a file c:\NewFile.txt, this property returns ".txt"."
So I looked up the code of the Extension property with reflector:
public string Extension
{
get
{
int length = this.FullPath.Length;
int startIndex = length;
while (--startIndex >= 0)
{
char ch = this.FullPath[startIndex];
if (ch == '.')
{
return this.FullPath.Substring(startIndex, length - startIndex);
}
if (((ch == Path.DirectorySeparatorChar) || (ch == Path.AltDirectorySeparatorChar)) || (ch == Path.VolumeSeparatorChar))
{
break;
}
}
return string.Empty;
}
}
It's check every char from the end of the filepath till it finds a dot, then a substring is returned from the dot to the end of the filepath.
[TestCase(#"C:/testfile.txt.gz", ".gz")]
[TestCase(#"C:/testfile.txt.gz.zip", ".zip")]
[TestCase(#"C:/testfile.txt.gz.SO.jpg", ".jpg")]
public void TestName(string fileName, string expected)
{
FileInfo info = new FileInfo(fileName);
string actual = info.Extension;
Assert.AreEqual(actual, expected);
}
All pass
It returns the extension from the last dot, because it can't guess whether another part of the filename is part of the extension. In the case of testfile.txt.gz, you could argue that the extension is .txt.gz, but what about System.Data.dll? Should the extension be .Data.dll? Probably not... There's no way to guess, so the Extension property doesn't try to.
The file extension starts at the last dot. Unfortunately, the documentation for FileSystemInfo.Extension doesn't answer that, but it logically must return the same value as Path.GetExtension, for which the documentation states:
Remarks
The extension of path is obtained by searching path for a period (.), starting with the last character in path and continuing toward the start of path. If a period is found before a DirectorySeparatorChar or AltDirectorySeparatorChar character, the returned string contains the period and the characters after it; otherwise, Empty is returned.
For a list of common I/O tasks, see Common I/O Tasks.
It would be nice there is an authoritative answer on file names in general, but I'm having trouble finding it.
How can I make, in File.Exist method put some filename that contains some numbers? E.g "file1.abc", "file2.abc", "file3.abc" etc. without using Regex?
Are you trying to determine if various files match the pattern fileN.abc where N is any number? Because File.Exists can't do this. Use Directory.EnumerateFiles instead to get a list of files that match a specific pattern.
Do you mean something like
for (int i = 1; i < 4; i++)
{
string fileName = "file" + i.ToString() + ".abc";
if (File.Exists(fileName))
{
// ...
}
}
new DirectoryInfo(dir).EnumerateFiles("file*.abc").Any();
or
Directory.EnumerateFiles(dir, "file*.abc").Any();
In the unix world, it is called globbing. Maybe you can find a .NET library for that? As a starting point, check out this post: glob pattern matching in .NET
Below is the code snap which will return all files with name prefix "file" with any digits whose format looks like "fileN.abc", even it wont return with file name "file.abc" or "fileX.abc" etc.
List<string> str =
Directory.EnumerateFiles(Server.MapPath("~/"), "file*.abc")
.Where((file => (!string.IsNullOrWhiteSpace(
Path.GetFileNameWithoutExtension(file).Substring(
Path.GetFileNameWithoutExtension(file).IndexOf(
"file") + "file".Length)))
&&
(int.TryParse(Path.GetFileNameWithoutExtension(file).Substring(
Path.GetFileNameWithoutExtension(file).IndexOf("file") + "file".Length),
out result) == true))).ToList();
Hope this would be very helpful, thanks for your time.
I am trying to port some Python code to .NET, and I was wondering if there were equivalents of the following Python functions in .NET, or some code snippets that have the same functionality.
os.path.split()
os.path.basename()
Edit
os.path.basename() in Python returns the tail of os.path.split, not the result of System.IO.Path.GetPathRoot(path)
I think the following method creates a suitable port of the os.path.split function, any tweaks are welcome. It follows the description of os.path.split from http://docs.python.org/library/os.path.html as much as possible I believe.
public static string[] PathSplit(string path)
{
string head = string.Empty;
string tail = string.Empty;
if (!string.IsNullOrEmpty(path))
{
head = Path.GetDirectoryName(path);
tail = path.Replace(head + "\\", "");
}
return new[] { head, tail };
}
I'm unsure about the way I'm returning the head and tail, as I didn't really want to pass out the head and tail via parameters to the method.
You're looking for the System.IO.Path Class.
It has many functions you can use to get the same functionality.
Path.GetDirectoryName(string)
For split, you'll probably want to use String.Split(...) on the actual path name. You can get the OS Dependant seperator by Path.PathSeparator.
In the case that im missing the point about os.path.split and you want the file name, use Path.GetFileName(string).
Please Note: You can explore all the members of the System.IO namespace by using the Object Browser (Ctrl+Alt+J) in Visual Studio. From here you go mscorlib -> System.IO and all the classes will be discoverable there.
It's like Intellisense on crack :)
os.path.basename()
The alternative is System.IO.Path.GetPathRoot(path);:
System.IO.Path.GetPathRoot("C:\\Foo\\Bar.xml") // Equals C:\\
Edit: The above returned the first path of the path, where basename should return the tail of the path. See the code below for an example of how this could be achieved.
os.path.split()
Unfortunately there's no alternative to this as there's no .Net equivalent. The closest you can find is System.IO.Path.GetDirectoryName(path), however if your path was C:\Foo, then GetDirectoryName would give you C:\Foo instead of C: and Foo. This would only work if you wanted to get the Directory Name of an actual file path.
So you'll have to write some code like the following to break these down for you:
public void EquivalentSplit(string path, out string head, out string tail)
{
// Get the directory separation character (i.e. '\').
string separator = System.IO.Path.DirectorySeparatorChar.ToString();
// Trim any separators at the end of the path
string lastCharacter = path.Substring(path.Length - 1);
if (separator == lastCharacter)
{
path = path.Substring(0, path.Length - 1);
}
int lastSeparatorIndex = path.LastIndexOf(separator);
head = path.Substring(0, lastSeparatorIndex);
tail = path.Substring(lastSeparatorIndex + separator.Length,
path.Length - lastSeparatorIndex - separator.Length);
}
Path.GetFileName
Path.GetDirectoryName
http://msdn.microsoft.com/en-us/library/beth2052.aspx
Should help
Why not just use IronPython and get the best of both worlds?
I'm using Path.Combine like so:
Path.Combine("test1/test2", "test3\\test4");
The output I get is:
test1/test2\test3\test4
Notice how the forward slash doesn't get converted to a backslash. I know I can do string.Replace to change it, but is there a better way of doing this?
As others have said, Path.Combine doesn't change the separator.
However if you convert it to a full path:
Path.GetFullPath(Path.Combine("test1/test2", "test3\\test4"))
the resulting fully qualified path will use the standard directory separator (backslash for Windows).
Note that this works on Windows because both \ and / are legal path separators:
Path.DirectorySeparatorChar = \
Path.AltDirectorySeparatorChar = /
If you run on, say, .NET Core 2.0 on Linux, only the forward slash is a legal path separator:
Path.DirectorySeparatorChar = /
Path.AltDirectorySeparatorChar = /
and in this case it won't convert backslash to forward slash, because backslash is not a legal alternate path separator.
Because your "test1/test2" is already a string literal, Path.Combine will not change the '/' for you to a '\'.
Path.Combine will only concat the 2 string literals with the appropriate path delimiter used by the OS, in this case Windows, which is '\', from there your output
test1/test2\test3\test4
Your best bet would be the string.Replace.
Try using the Uri class. It will create valid Uris for the correct target machine (/ -> \).
First, I would argue in this particular case, it wouldn't be unreasonable to do a single .Replace()
Secondly, you could also use System.Uri to format your path, it's very strict. However, this will be more lines than a simple .Replace(). I apperently am voting for you to just use .Replace() be done with it! Hope that helps
Try using
var fullname = new DirectoryInfo(Path.Combine("f:/","test1/test2", "test3\\test4")).FullName;
This will result in
f:\test1\test2\test3\test4
Using .NET Reflector, you can see that Path.Combine doesn't change slashes in the provided strings
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);
}
You can do the same with String.Replace and the Uri class methods to determine which one works best for you.
If you need your result to have forward slashes instead of backward slashes, and if your first path component is absolute (i.e. rooted) path, you could actually combine it using the Uri class:
string CombinedPath = new Uri(new Uri("C:/test1/test2"), "test3\\test4").AbsolutePath;
Note that this won't work if the first component is relative path too.
No, the seperator character is defined as a Read Only.
http://msdn.microsoft.com/en-us/library/system.io.path.pathseparator.aspx
You should use a Replace as it's a trivial change.
I am somehow unable to determine whether a string is newline or not. The string which I use is read from a file written by Ultraedit using DOS Terminators CR/LF. I assume this would equate to "\r\n" or Environment.NewLine in C#. However , when I perform a comparison like this it always seem to return false :
if(str==Environment.NewLine)
Anyone with a clue on what's going on here?
How are the lines read? If you're using StreamReader.ReadLine (or something similar), the new line character will not appear in the resulting string - it will be String.Empty or (i.e. "").
Are you sure that the whole string only contains a NewLine and nothing more or less? Have you already tried str.Contains(Environment.NewLine)?
The most obvious troubleshooting step would be to check what the value of str actually is. Just view it in the debugger or print it out.
Newline is "\r\n", not "/r/n". Maybe there's more than just the newline.... what is the string value in Debug Mode?
You could use the new .NET 4.0 Method:
String.IsNullOrWhiteSpace
This is a very valid question.
Here is the answer. I have invented a kludge that takes care of it.
static bool StringIsNewLine(string s)
{
return (!string.IsNullOrEmpty(s)) &&
(!string.IsNullOrWhiteSpace(s)) &&
(((s.Length == 1) && (s[0] == 8203)) ||
((s.Length == 2) && (s[0] == 8203) && (s[1] == 8203)));
}
Use it like so:
foreach (var line in linesOfMyFile)
{
if (StringIsNewLine(line)
{
// Ignore reading new lines
continue;
}
// Do the stuff only for non-empty lines
...
}