As a web developer, I rarely need to deal with physical files on disk. It’s a request/response kind of life while communicating with a database engine. When working on this blog, I find myself doing more file manipulation, as I’m dealing with markdown files, images, and other static assets. It’s a mix of Ruby, .NET Core, and JavaScript. .NET Core is my first choice to script things. In this post, we’ll look at the Path class found under the System.IO namespace and some of the methods I regularly use.

The Path class in .NET is a fantastic helper class that doesn’t get the love it deserves. We should always think about using Path when dealing with files or the need to create assets on disk. Let’s start by looking at two standard variables we may have in our codebase: a directory path and a file name.

var path = "/users/khalidabuhakmeh/project";
var image = "lolcat.png";

The values aren’t necessarily important, but they are conventional strings we may have to deal with when working with the file system. Let’s explore some methods found on Path in no particular order.


The GetExtension method is powerful, as it can correctly find the extension of a file name regardless of the path given to it. In our case, we can determine that the extension on lolcat.png is .png.

var extension = Path.GetExtension(image);
Console.WriteLine($"extension: {extension}");
// extension: .png

Note that GetExtension returns a value with a starting . in front of the file type.


A recent addition to the .NET Framework, the ChangeExtension method was a pleasant surprise. It’s addition almost negates the need for GetExtension in many cases, as we might typically use GetExtension to build a new destination file. To use the ChangeExtension method, we need only pass in the full path to a file, and the extension we want.

var txt = Path.ChangeExtension(image, "txt");
Console.WriteLine($"txt: {txt}");
// txt: lolcat.txt

The ChangeExtension implementation can also handle if the extension method value starts with a . or whether it doesn’t.

var txt = Path.ChangeExtension(image, ".txt");
Console.WriteLine($"txt: {txt}");
// txt: lolcat.txt

The .NET Framework takes care of the edge case where our extension values may be inconsistent.


The Combine method is the most used feature of Path. It can combine multiple paths and files into a useable value. We don’t have to worry about separators at the beginning or end of our values. The Combine method also has a params overload, meaning it will handle a large number of path values.

var combine = Path.Combine(path, image);
Console.WriteLine($"combine: {combine}");
// combine: /users/khalidabuhakmeh/project/lolcat.png

This method is useful when transferring files from one directory to another, and we need to create a new target destination using the same filename and target directory.


Relative paths are essential to use when we want to store path values. Our code may run in multiple environments, making root folder names unpredictable. We can convert absolute paths to relative paths using the GetRelativePath method.

var relative = Path.GetRelativePath(path, "/users");
Console.WriteLine($"relative: {relative}");
// relative: ../..


When dealing with a fully qualified path, we may want to “pop” the filename from the end. We could write a complicated statement that splits our path, breaking our string at the separators and takes the last value, or we could use GetFileName.

var combine = Path.Combine(path, image);
var filename = Path.GetFileName(combine);
Console.WriteLine($"filename: {filename}");
// filename: lolcat.png

This method is useful for retrieving and storing the filename in a data store. I’ve used this personally when moving files from local disk up to a cloud storage provider and needing to retain the original filename.


Naming our filenames with descriptive names can give us the benefit of additional metadata. Using GetFileNameWithoutExtension strips the extension and the path of a file, only returning the file name.

var withoutExtension = Path.GetFileNameWithoutExtension(image);
Console.WriteLine($"without extension: {withoutExtension}");
// without extension: lolcat

This method can be useful when displaying the file to a user who may already understand the types of data they are looking at, i.e., images, text files, or videos.


If we are processing and generating files in bulk, the GetRandomFileName method is beneficial. The method returns a cryptographically strong random 8.3 string (eight characters, then a dot, then three more characters) that we can use as either a folder or a file name.

var random = Path.GetRandomFileName();
Console.WriteLine($"random: {random}");
// random: 05y5xhb4.wt2

Note that the random value also includes what appears to be a random file extension. Changing the file extension will affect the cryptographic strength of the name.


This next method is similar to GetRandomFileName but behaves much differently. The GetTempFileName method creates a 0-byte file on disk before returning the full path to the generated asset. That means this method performs an I/O operation.

var temp = Path.GetTempFileName();
Console.WriteLine($"temp: {temp} | exists: {File.Exists(temp)}");
// temp: /var/folders/0h/bb4ssg9s2_d482xfx6dq43l00000gn/T/tmpv8oOvk.tmp | exists: True

Using GetTempFileName ensures that we either get a file or get an exception early in our code. Nothing could be worse than performing expensive file operations only to find we can’t write our work to disk.


One of the most significant advantages of the .NET Framework is it has practical classes and namespaces that make us productive. Managing files and directories is a mostly solved problem in .NET, and we should be using types like Path. If we need to do more than deal with paths, there are also the types of File and Directory to help round out all the functions we need.

I hope you found this post helpful and feel free to leave a comment below.