Using File Functions in .NET

When someone mentions file handling in an application, what kind of things go through your mind as a developer?

To many folks, file handling is nothing more than opening a file, reading its contents, and then closing it. .NET, however, has a very rich static class with lots of useful stuff in for doing all manner of file-related things.

Most of the functionality can be divided into the following groups:

Modification

Operations

Creation

Ciphering

Attribute Handling

Reading

At last count, there were over 50 methods available in the static class (reduce that by about 10 to take into account overrides).

File Operations

An operation is defined as a one-step process that results in a major change. Copying, moving, and deleting are all operational methods, and easily used in a .NET program. Create yourself a simple console mode application in Visual Studio, then create yourself a file we can test with, just a simple text file. For my purposes, I'm going to put it in the root of my d: drive and call it test.txt.

As you can see from Figure 1, I have a file ready to test with:

Figure 1: A test file is ready

Now, add the following line of code to your console mode program (remembering to change file names where needed).

File.Copy("d:\\test.txt", "d:\\testcopy.txt");

Before we go any further, it's worth me mentioning that I won't be doing any error handling in this post. You should, however, ALWAYS make sure you have the correct code in place (especially in production) to detect and deal with errors when dealing with file systems. There is a lot that can go wrong, very quickly, when working with files.

If you run your application with the preceding copy code, you should see that a copy of your file appears. Simple as that. No magic, nothing difficult.

You might notice that I've used '\\' instead of just '\' as a path separator. I've done this because C# will, unless you prefix your string with a @ symbol, try and decode a single '\' as a control code, so you need to double them up to escape them.

File.Copy also takes a third parameter, a boolean flag which forces an overwrite. If you run your program again, you should find that it throws an exception. If you, however, modify your code as follows:

File.Copy("d:\\test.txt", "d:\\testcopy.txt", true);

and then run it again, this time you should see that it does not. You can set the flag to false if you wish, but because the default is to not try and overwrite, as you've seen, omitting the parameter has the same effect.

The next operation is move, and, like copy, it takes a source and destination file name. Move, however, has no third parameter to force a move; this means you can't force a move to occur if the destination file exists, you can use move as follows:

File.Move("d:\\test.txt", "d:\\testmove.txt");

If you need to move a file to a name that already exists, you'll first need to determine if it exists, and that can easily be achieved by using 'File.Exists' that will return a boolean flag. The flag will be true if the named files exists false; if it does not, you can easily use in an if/then as follows:

if(File.Exists("d:\\test.txt")
{
Console.WriteLine("File exists");
}

Figure 2: Checking for an existing file

Within this check, you easily can use 'File.Delete' before executing a move, something like the following:

And that's it for the operational commands. As you can see, they're all very easy to use, and designed for quite quick operation with regular files. Because their file system-aware, they can be used across UNC path names, and multiple file system types as long as that file system can be exposed as a regular file.

.NET has an abundance of ways to create a file, and most developers tend to use the "TextWriter" and "BinaryWriter" interfaces; however, you easily can just use "File.Create", thus eliminating the need for nesting IDisposable objects and possibly having complex logic in your loops to manage the reading/writing process.

"File.Create" drops straight down to the standard native/legacy Win32 create file routines, so they are fast, efficient, and versatile. You can create a file simply, as follows:

will create a file handle that will take 4k worth of data before being written to disk, and with no extra options. There's a lot of variation to be had in the options, so it's worth reading the MSDN page describing the method that lists the various options available and what they do.

In between the create and the close calls, you have the regular functionality you would expect from a file stream, such as the ability to read/write data in the file, seek the file, and so forth. I'll leave a more in-depth description of that for another time, however.

If all you want to do is quickly and simply write some data from memory into a file, you can use "File.WriteAllLines", as follows

Which, if you run in your console mode program, and then examine the file location, you should see it will create a simple text file.

Figure 3: The simple text file is visible

If you want to read the file back in, it's as simple as the following:

IEnumerable<string> myText =
File.ReadAllLines(@"d:\writetest.txt");

This, then, gives you a standard IEnumerable list that you can iterate and process in any way you like.

The file class also has the following methods:

FIle.WriteAllBytes

File.WriteAllText

File.ReadAllBytes

File.ReadAllText

The byte versions read and write full byte arrays, and the 'text' versions write everything as a whole string rather than a sequence of clearly defined strings. The calls can handle any size file the underlying file system can handle; the only limit is how much memory you have to process the contents.

which, if you run after the write all lines example previously, should give you the following:

Figure 4: The second simple text file

There are again, as with other calls, a number of overrides that allow you to do things like specify text encoding. There are, however, ONLY append methods for lines and text; there is no append functionality for a bytes-orientated file.

One quite interesting feature the file class has is the ability to encrypt and decrypt files. This uses the built-in operating system security functionality, and the encryption is specific to the user account under which it was performed.

If we perform the following:

File.Encrypt(@"d:\writetest.txt");

on the file we created in the write all lines example, and then look at our Windows Explorer, we'll see the filename's colour changes to green, showing that this is now an encrypted file.

Figure 5: The green text tells you the file's encrypted

If you click this file, it will be transparently decrypted and loaded into your default text editor. If you make a change and save it again, the file will remain encrypted.

If someone else, using a different account, was able to gain access to this file, however, they would not be able to decrypt it. This is important because it also means that if you encrypt a file, delete your user account, and then re-create the account, you will NOT be able to decrypt the file, so although it's useful for day to day security, you should always keep an un-encrypted version in a safe place.

You can easily decrypt the file by using the following:

File.Decrypt(@"d:\writetest.txt");

You should observe that when you run that from your console app, that the colour of your file in Explorer changes back to its original colour.

The remaining methods deal with the many attributes that are attached to files in a typical Windows system. Under NTFS file systems, you have a great amount of control with regard to setting and changing a files security permissions with the

File.GetAccessControl

File.SetAccessControl

methods. This is another area where reading the MSDN page is a very good idea, because there are many different flags, settings, and owner/sharing permissions you can specify.

Among the many other attributes are things like the file's original creation time, the last time it was modified, and what time it was last accessed, but not modified (for example, Read) there are also overrides of these methods that will return those times converted to universal format, which is often needed if you're dealing with files that can be read from and written to anywhere in the world as part of a server system, for example.

As you can see, there's lot's more hidden under the surface than might appear at first glance. In a future post, we'll continue this exploration and take a closer look at a close relation to the "File" class, and that's its cousin, "Directory".

Got a burning question on .NET? Something you want to know about .NET? Or, curious if there exists functionality to achieve a given task? Feel free to hunt me down on Twitter as @shawty_ds or in the Lidnug users group on .NET that I help run. If I can, I'll feature your question in this column.