Introduction
Working with paths and the file system is an important concept and it's a part of every programming language to have a way to deal with file paths, directory paths and files in general. So what is the Path static class available in C#? What does it do? What can it help us with? And what problems does it solve? Well, this is what we'll be answering in this post, so buckle up because this will be a lengthy journey towards the Path.
Table of contents
1: Overview of the System.IO.Path Class
2: Why is it useful
3: Important point to know
4: Exploring the Path Class
5: What's Next
6: An idea for the experienced
7: Conclusion
Overview of the Path Class
The Path class is part of the namespace System.IO, this namespace has specialized utility classes for dealing with Input and Output related operations. From the name of it, we can easily tell that this class is meant to work with paths! Dang, what gave it away?
Before we dive deeper, let's first look at a quick definition of a path.
According to Microsoft Learn, a path has the following definition:
💡 A path is a string that provides the location of a file or directory. A path does not necessarily point to a location on disk; for example, a path might map to a location in memory or on a device.
Now that's clear and pretty straightforward, exactly what we need here.
The Path class is a static class, which means we don't have to create an instance of it to make use of its methods and properties, just like the Console class from the System
namespace.
Why is it useful?
You may not hear about this class if you're still a new comer to the language, but I've noticed that it shows up from time to time, and take my word for it, it's important to know, because one day in the future, you'll need to do some file related work, and surprise surprise, the Path class is involved.
This class operates on strings, most of its methods accept a string parameter representing a path to a file, and through plenty of methods and properties which we'll be exploring in a bit, you can perform dozens of operations on file paths, from renaming files, getting file extensions, getting random file names, or maybe grabbing the full path of an existing file, this class is the jack of all trades when it comes to file paths.
If you are not yet convinced about it, I'll tell you about a real world scenario, where I had to use this class to achieve a task.
I was working on my personal blogging website, and I wanted to add a way to upload images, now that seems easy and simple enough, but in reality, you can't just accept what a user decides to upload at face value, I needed a way to check that the uploaded file follows certain rules, like the file name must be changed so that duplication doesn't occur, and also, the file extension, if I'm prompting a user to upload a cover image, then I don't think I should get a .exe or .pdf in return? (Even though that user is only me, but that doesn't mean I have to trust myself) 😅
Ok I'm sure you're convinced now, let's move on...
An important point to know
The Path class is platform-dependent, and that's great!
In the case of operating systems, each OS has its own unique way of defining file paths, some use forward slashes (/) as a path separator, while others use backslashes (\).
Mac Os & Linux use forward slashes while Windows use backslashes, now that's important to note because if you were developing your project on a Windows machine, and you hardcoded the file paths, migrating this codebase to a different Os (i.e Mac Os or Linux), will lead to malfunctioning code, because the paths you coded, don't follow the rules of the new Os. That's why you shouldn't hardcode file paths and instead use the Path class to handle the work for you, because by doing so, you are ensuring that the provided path will always work regardless of the platform.
So even if you switch from Windows to a UNIX based OS, the Path class will say, Oh, this platform uses forward slashes for file paths separator and therefore I should change up the paths a bit to fit the criteria for this OS.
Exploring the Path class
Enough theory, let's get into the code part, I'll use a Console Application for the code examples, feel free to follow along and play around as we go through the code samples.
Console.WriteLine(Path.DirectorySeparatorChar);
// OUTPUT: /
Beginning with this field, it returns the character used to separate file paths, the one we talked about in the previous point, in my case, it's a forward slash.
Console.WriteLine(Path.GetFullPath("Program.cs"));
//OUTPUT: /Users/Shared/Demos/PathDemo/bin/Debug/net7.0/Program.c
The GetFullPath
method here, returns the absolute path of a particular string path, I passed the name of the file in which I'm writing the code, and it outputs to me the whole path from the beginning.
Console.WriteLine(Path.GetExtension("Program.cs"));
//OUTPUT: .cs
I like the naming of the class members, everything is self explained. The GetExtension
method is pretty handy when it comes to checks such as the scenario I explained earlier regarding image uploads, let's use something similar as an example to better understand the use cases of this class:
string[] allowedFileExtensions = { ".png", ".jpeg", ".jpg" };
string imageFileName = "files/images/cool-image.png";
if(allowedFileExtensions.Contains(Path.GetExtension(imageFileName))) {
Console.WriteLine("File Accepted");
}
else {
Console.WriteLine("File Unaccepted");
}
This code sample demonstrates a good use case of the GetExtension
method.
- First of, we're declaring an array of accepted file extensions for our image
- Secondly, we're creating an arbitrary file path for an image called cool-image.png.
- Lastly, we are checking wether the array contains the extension of the file, the condition might seem lengthy, but it's pretty easy, we could've simplified by storing the file extension in a separate variable, and then checked if it's contained in the array, which would've worked too.
If you run the program now, you should see the message File Accepted, that's because our arbitrary file is a png file, which is one of the acceptable formats defined in our array. Try changing the extension to something else like .pdf, and you would be able to see the other message being printed out.
We'll now use two other classes, Directory
and File
, these two go along very well with our Path
class, they all come from the same namespace too, so you can say they're like a bond, the IO bond, now I know this is a bit of a diversion from our main class, but still, in an actual codebase, you would want to use all of them together.
string fileName = "data.json";
string dataFilePath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
File.Create(dataFilePath);
Let me briefly explain what's going on:
- At the top, I'm creating a string variable called
fileName
. - After that, I'm using the
Combine
method of thePath
class to combine two paths together, this is the method you should use to string together a path for a directory or a file, because as you remember, we shouldn't hardcode literal paths in the code so that we don't face operating system issues. - Lastly, using the
Create
method of theFile
class, we'll create the file in the output directory, theGetCurrentDirectory
method in the context of a Console application returns the path of the output directory, and the reason we need to create our arbitrary data file in the output directory is because it's necessary in case we wanted to actually fetch data from it.
// Console.WriteLine(Path.GetInvalidPathChars());
Console.WriteLine(Path.GetInvalidFileNameChars());
//OUTPUT: /
These two methods get invalid characters that you shouldn't be using in a path or a file name, the reason I commented out the first one is because on Mac Os, the output of this is null, but if you want to know your platform restrictions, run these two lines to see what invalid characters that you shouldn't include in your paths.
string firstFile = "first-file.txt";
string duplicateFile = "first-file.txt";
if(firstFile.Equals(duplicateFile)) {
Console.WriteLine("It's not safe to create these files");
}
else {
Console.WriteLine("It's safe to create these two files");
}
File.Create(firstFile);
File.Create(duplicateFile);
This code compares two arbitrary file names, both have the same name and extension, and creating both could cause an exception to be thrown, like the following:
⛔️ System.IO.IOException: "The process cannot access the file '/Users/Shared/Demos/PathDemo/bin/Debug/net7.0/first-file.txt' because it is being used by another process."
But maybe we mistook the extensions, perhaps the initial intention was to create two files, each with a unique extension, let's see how we can do that.
duplicateFile = Path.ChangeExtension(duplicateFile, "json");
Now creating this file, doesn't throw an exception.
string path = "/Files/Images";
if(Path.HasExtension(path)) {
Console.WriteLine($"This path leads to a ({Path.GetExtension(path)}) file");
}
else {
Console.WriteLine($"This path leads to a directory probably");
}
//OUTPUT: This path leads to a directory probably
The HasExtension
method checks wether a given path contains an extension for a file or not, the code sample creates a path without an extension and then uses the method to check for it.
string fileName = "people.json";
Console.WriteLine(Path.IsPathFullyQualified(fileName)); // False
Console.WriteLine(Path.ChangeExtension(fileName, "txt")); // people.txt
Console.WriteLine(Path.GetRandomFileName()); // znatqlmf.u5n
Lastly, this snippet showcases the output of some other methods available for you to use.
What's Next?
Phew! That was a long one, but don't let that get to you, as there's still plenty to unravel and stuff to experiment with, putting them all in a single article would make it very tedious and it'll probably end up in your reading list for months until you forget about it, so I want to challenge you a little, I want you to exploit your IDE to learn what's also available for this class. IDEs like Visual Studio and VS Code have a great extension for C# development called Intellicode which aids you in writing code by either providing suggestions, or display details about the code elements you are working with, like for a method, the intellicode dialog would show you what arguments does the method accept, what it returns, and a brief explanation of what it does, now I want you to make use of this to further explore the rest of the methods I haven't included in this guide.
An idea for the experienced
If you're seasoned in a certain area such as web development, desktop development in C#, then try doing this task:
Create a project to save files in a particular directory on your machine, make a button for the user to choose a group of files to upload them, and make sure to enforce rules on the file names, file extensions, and file size and count, you will want to do some research on your own, but it'll be worth it, because once you do it, it'll be a system you can use in multiple projects. I think a Web API project is sufficient enough, because it'll abstract away all the UI related work and leave you with the C# logic only, so give it a go, and see how would build a file upload system. This task will give you the chance for hands on work with the
Path
class which is the point of this post and task. Good Luck!
Conclusion
Throughout this post, we went over the important aspects of the Path
class, we explained some theory and explored some of its methods and properties. Other static classes made it to the show, like File
and Directory
, as you saw during the post, the methods of each class are very well named and pretty much self explained, so try having a go with the other two classes, play around and see what you can do if you put all three together. That's it for this post, I hope you enjoyed it and learned something new! 👋🏻
...