(How)
C^# You Are - 18 March 2007
This week's questions cover streams and IO.
- How do you detect the end of a stream when reading? Answer
If you are using a StreamReader then the EndOfStream
property will tell you when you've reached the end of the stream. Additionally
when you call one of the Read methods the return value can be an
indicator. For example ReadLine will return null
when there is nothing more to read while Read will return 0.
- Assuming you are reading a text file, how do you read an entire line at a time? Answer
If you are reading a text file then you are probably using StreamReader
or one of its derived classes. In this case the ReadLine
method can be used to read an entire line at a time. The line terminator is
not included in the returned string.
- How can you convert a string to a stream? How about arbitrary memory? Answer
The StringReader/StringWriter classes can be used to read/write
strings as though they are streams. For arbitrary memory locations use
MemoryStream. MemoryStream is especially useful
when you are streaming data from another source (such as a database). You
can stream the data into a memory buffer and then get the memory buffer using
GetBuffer.
- How does the FileStream class differ from a normal StreamReader? Answer
FileStream is used to read and write files in the file system.
StreamReader is used to read an arbitrary text stream. The
biggest difference, other than the fact that one is read/write while the other is
read, is that FileStream is a true stream while StreamReader
is not. StreamReader derives from TextReader.
Neither of which are streams.
This can cause a problem because most methods that accept files as input also accept
Stream objects. However a StreamReader can
not be used for that purpose as it isn't a stream. The workaround is to either
expose another overload of a method to accept a TextReader object
or to use the reader's BaseStream property to access the underlying
stream.
- How do you determine if a file exists? Answer
The File.Exists method will determine if a file exists. The
following code is commonly used.
public string ReadFile ( string fileName )
{
if (File.Exists(fileName))
return File.ReadAllText(fileName); //It is safe
now
}
This code looks correct but it technically isn't. The problem is that you
are assuming that if the method succeeds then the file exists. This might
be true at the time the method is called but after it is called, but before you
do any work, the file might be deleted. Therefore use File.Exists
only as a quick way of determining potential success rather than assuming that if
it succeeds that the file truly does exist. Use
exception handling where appropriate
even with the check.
Alternatively you can use FileInfo to create an instance of a file
and then use the Exists property to determine if it exists.
- How can you search for a specific set of files in a directory (such as all *.txt
files)? Answer
DirectoryInfo.GetFiles can be used to search for all files with
a specific name. The overloaded version accepting a SearchOption
parameter can be used to recursively search subdirectories as well. Be careful
about this method though. It searches both the long and short filenames.
Therefore if you search for a file called 123*.txt you would get all files called
123*.txt but also any files with a short filename of 123*.txt as well. Since
short filenames do not always match the long filename you should compare the resulting
filenames to ensure they match your originally search criteria.
- How do you determine the size of a directory? A directory and all its children? Answer
Surprisingly there is no method in .NET to get the size of a directory. You
can create one though using the following code (it is not thread safe).
public long GetDirectorySize ( string directory, bool recurse )
{
DirectoryInfo dir = new DirectoryInfo(directory);
long size = 0;
if (dir.Exists)
{
SearchOption option = recurse ? SearchOption.TopDirectoryOnly
: AllDirectories;
foreach (FileInfo fi in dir.GetFiles("*", option))
size += fi.Length;
};
return size;
}
The downside to this method is twofold. Firstly for large directories it can
take quite a while. If you every view the properties of a folder in Windows
Explorer you will notice that the enumeration process to calculate file size actually
occurs on a separate thread so the main UI can continue on. You should consider
this approach in your own applications.
The second problem is that if the user does not have access to a file or directory
for any reason then an exception will occur and the method will fail. Ideally
you should use exception handling to ignore files that can not be accessed.
- How do you get the different parts of a file path (the path, the file name and the
extension)? Answer
The Path class contains methods to get the different parts of a
path. The methods GetDirectoryName, GetFileNameWithoutExtension
and GetExtension can be used to get the desired information.
- What purpose does the FileInfo class serve? Answer
This class is used to represent a single file and its basic attributes such as name,
size and file attributes. Directories use the DirectoryInfo
class. The file/directory does not necessarily exist however. Use the
Exists property to determine if the file/directory is an actual
file system object.
- How do you copy the contents of a directory to another directory? Recursively? Answer
.NET only supports moving directories. To copy a directory you need to use
either FileInfo.Copy or File.Copy to copy each
file in the source directory to the target directory. You first need to create
the target directory though.
To recursively copy a directory you need to repeat this process for each subdirectory.
public static void CopyDirectory ( string source, string target, bool recurse )
{
//Create the target as needed
if (!Directory.Exists(target))
Directory.CreateDirectory(target);
//Copy the files
foreach(string file in Directory.GetFiles(source))
{
File.Copy(file, Path.Combine(target, Path.GetFileName(file)), true);
};
//Copy subdirectories, if needed
if (recurse)
{
foreach (string child in Directory.GetDirectories(source))
{
CopyDirectory(child, Path.Combine(target, Path.GetFileName(child)), true);
};
};
}
As with the earlier code this code is slow, will fail if a security exception occurs and is not thread safe. It does give you a baseline for creating a more robust version
though.