- File I/O
- Creating a Directory
- Stream Handling
- Yet More on Exception Handling
- Binary Data File I/O
- Conclusion
Creating a Directory
Listing 1 illustrates the basic iterative approach I use in this article.
Listing 1The basic approach.
using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; namespace List_Files { class Program { static void Main(string[] args) { string directoryName = "A subdirectory"; Console.WriteLine("This program lists all the files in the directory."); System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(@"C:\"); dir.CreateSubdirectory(directoryName); foreach (System.IO.FileInfo file in dir.GetFiles("*.*")) { Console.WriteLine("{0}, {1}", file.Name, file.Length); } Console.ReadLine(); Directory.Delete(@"C:\" + directoryName); } } }
In Listing 1, I display all the filenames (and their sizes) contained in the root C: drive. I also create a new subdirectory called A subdirectory on the C: drive. Then, at the end of the program, I clean up by deleting the new subdirectory. Because the line Console.ReadLine() stops the program until the user types something, the call to the directory-deletion code doesn't occur until the user provides the instruction (for example, by pressing the Enter key). You can open Windows Explorer and verify that the subdirectory is correctly createdjust before it then gets deleted!
However, the code in Listing 1 is far from perfect. One of the most important omissions from Listing 1 is exception handling. For example, let's say that the subdirectory is deleted manually before the C# code gets a chance to remove it. What happens to the code in Listing 1? An I/O exception is what happens, as shown in Figure 1.
Figure 1 Our first I/O exception.
We manually deleted the folder in advance of the C# code deleting it. There's no folder for the C# code to delete; in this situation, the runtime system does its best to compensate, which it does by throwing an exception. This leads to the first major point in this article:
Rule #1: Always code disk I/O defensively.
In other words, don't assume too much about the disk on which your code operates. Let's improve the code in Listing 1 by adding some exception handling, as illustrated in Listing 2.
Listing 2The code from Listing 1, with I/O exception handling added.
namespace List_Files { class Program { static void Main(string[] args) { string directoryName = "A subdirectory"; Console.WriteLine("This program lists all the files in the directory."); try { System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(@"C:\"); dir.CreateSubdirectory(directoryName); foreach (System.IO.FileInfo file in dir.GetFiles("*.*")) { Console.WriteLine("{0}, {1}", file.Name, file.Length); } Console.ReadLine(); Directory.Delete(@"C:\" + directoryName); } catch (IOException exception) { Console.WriteLine("Got an exception: " + exception.Message); Console.ReadLine(); } } } }
This time, when we run the program and manually delete the folder in advance of the C# code deletion, we see the following exception message:
Got an exception: Could not find a part of the path 'C:\A subdirectory'.
This response means that the C# code is working to keep its own house in order. In other words, if an error condition arises, the code correctly handles the situation and fails gracefully. This design is generally better than passing the responsibility to the runtime system. A program that handles its own exceptions is generally a better citizen than one that leaves that job to the runtime system.
Notice in Listing 2 that I used an IOException. This is part of the exception-handling strategy, and I can generalize it by instead just catching instances of Exception. This approach to exception handling allows the code to handle a broader range of exceptions. In many cases, code must do more than just print out a message when an exception occurs. For example, if a network message has been sent and then an exception occurs, it's possible that the message should be re-sent.
The main point here is that effective exception handling is often inextricably interwoven with the business logic of your application. For this reason, exception handling should always be considered carefully during the design phase. I've seen production code that catches exceptions and then just silently carries on executing. This poor design merely sidesteps the problem. In my opinion, a better approach is to at least make a note of the exception. This thinking leads to the next major point in this article:
Rule #2: Effective exception handling is a skilled design exercise.
This rule applies to I/O as well as any other area of programming.