Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
48
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

158 C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

// Open the file in read-only mode.

using (FileStream fs = new FileStream("test.txt", FileMode.Open))

{

using (StreamReader r = new StreamReader(fs, Encoding.UTF8))

{

// Read the data and convert it to the appropriate data type. Console.WriteLine(Decimal.Parse(r.ReadLine())); Console.WriteLine(r.ReadLine()); Console.WriteLine(Char.Parse(r.ReadLine()));

}

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

5-8. Read and Write a Binary File

Problem

You need to write data to a binary file, with strong data typing.

Solution

Create a new System.IO.FileStream object that references the file. To write the file, wrap the FileStream in a System.IO.BinaryWriter and use the overloaded Write method. To read the file, wrap the FileStream in a System.IO.BinaryReader and use the Read method that corresponds to the expected data type.

How It Works

The .NET Framework allows you to write or read binary data with any stream by using the BinaryWriter and BinaryReader classes. When writing data with the BinaryWriter, you use the BinaryWriter.Write method. This method is overloaded to support all the common C# .NET data types, including strings, chars, integers, floating-point numbers, decimals, and so on. The information will then be encoded as a series of bytes and written to the file. You can configure the encoding used for strings by using an overloaded constructor that accepts a System.Text.Encoding object, as described in recipe 5-7.

You must be particularly fastidious with data types when using binary files. This is because when you retrieve the information, you must use one of the strongly typed Read methods from the BinaryReader. For example, to retrieve decimal data, you use ReadDecimal. To read a string, you use ReadString. (The BinaryWriter always records the length of a string when it writes it to a binary file to prevent any possibility of error.)

The Code

The following console application writes and then reads a binary file.

C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

159

using System; using System.IO;

namespace Apress.VisualCSharpRecipes.Chapter05

{

static class Recipe05_08

{

static void Main()

{

// Create a new file and writer.

using (FileStream fs = new FileStream("test.bin", FileMode.Create))

{

using (BinaryWriter w = new BinaryWriter(fs))

{

// Write a decimal, two strings, and a char. w.Write(124.23M);

w.Write("Test string"); w.Write("Test string 2"); w.Write('!');

}

}

Console.WriteLine("Press Enter to read the information."); Console.ReadLine();

// Open the file in read-only mode.

using (FileStream fs = new FileStream("test.bin", FileMode.Open))

{

// Display the raw information in the file. using (StreamReader sr = new StreamReader(fs))

{

Console.WriteLine(sr.ReadToEnd());

Console.WriteLine();

// Read the data and convert it to the appropriate data type. fs.Position = 0;

using (BinaryReader br = new BinaryReader(fs))

{

Console.WriteLine(br.ReadDecimal());

Console.WriteLine(br.ReadString());

Console.WriteLine(br.ReadString());

Console.WriteLine(br.ReadChar());

}

}

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

160 C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

5-9. Read a File Asynchronously

Problem

You need to read data from a file without blocking the execution of your code. This technique is commonly used if the file is stored on a slow backing store (such as a networked drive in a wide area network).

Solution

Create a separate class that will read the file asynchronously. Start reading a block of data using the FileStream.BeginRead method and supply a callback method. When the callback is triggered, retrieve the data by calling FileStream.EndRead, process it, and read the next block asynchronously with

BeginRead.

How It Works

The FileStream includes basic support for asynchronous use through the BeginRead and EndRead methods. Using these methods, you can read a block of data on one of the threads provided by the

.NET Framework thread pool, without needing to directly use the threading classes in the

System.Threading namespace.

When reading a file asynchronously, you choose the amount of data that you want to read at a time. Depending on the situation, you might want to read a very small amount of data at a time (for example, if you are copying it block by block to another file) or a relatively large amount of data (for example, if you need a certain amount of information before your processing logic can start). You specify the block size when calling BeginRead, and you pass a buffer where the data will

be placed. Because the BeginRead and EndRead methods need to be able to access many of the same pieces of information, such as the FileStream, the buffer, the block size, and so on, it’s usually easiest to encapsulate your asynchronous file reading code in a single class.

The Code

The following example demonstrates reading a file asynchronously. The AsyncProcessor class provides a public StartProcess method, which starts an asynchronous read. Every time the read operation finishes, the OnCompletedRead callback is triggered and the block of data is processed. If there is more data in the file, a new asynchronous read operation is started. AsyncProcessor reads 2 kilobytes (2,048 bytes) at a time.

using System; using System.IO;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter05

{

public class AsyncProcessor

{

private Stream inputStream;

// The amount that will be read in one block (2 KB). private int bufferSize = 2048;

C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

161

public int BufferSize

{

get { return bufferSize; } set { bufferSize = value; }

}

// The buffer that will hold the retrieved data. private byte[] buffer;

public AsyncProcessor(string fileName)

{

buffer = new byte[bufferSize];

// Open the file, specifying true for asynchronous support. inputStream = new FileStream(fileName, FileMode.Open,

FileAccess.Read, FileShare.Read, bufferSize, true);

}

public void StartProcess()

{

// Start the asynchronous read, which will fill the buffer. inputStream.BeginRead(buffer, 0, buffer.Length,

OnCompletedRead, null);

}

private void OnCompletedRead(IAsyncResult asyncResult)

{

//One block has been read asynchronously.

//Retrieve the data.

int bytesRead = inputStream.EndRead(asyncResult);

// If no bytes are read, the stream is at the end of the file. if (bytesRead > 0)

{

//Pause to simulate processing this block of data. Console.WriteLine("\t[ASYNC READER]: Read one block."); Thread.Sleep(TimeSpan.FromMilliseconds(20));

//Begin to read the next block asynchronously. inputStream.BeginRead(

buffer, 0, buffer.Length, OnCompletedRead, null);

}

else

{

// End the operation. Console.WriteLine("\t[ASYNC READER]: Complete."); inputStream.Close();

}

}

}

}

162 C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

Usage

The following example shows a console application that uses AsyncProcessor to read a 2-megabyte file.

using System; using System.IO;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter05

{

static class Recipe05_09

{

static void Main(string[] args)

{

// Create a 2 MB test file.

using (FileStream fs = new FileStream("test.txt", FileMode.Create))

{

fs.SetLength(1000000);

}

//Start the asynchronous file processor on another thread. AsyncProcessor asyncIO = new AsyncProcessor("test.txt"); asyncIO.StartProcess();

//At the same time, do some other work.

//In this example, we simply loop for 10 seconds. DateTime startTime = DateTime.Now;

while (DateTime.Now.Subtract(startTime).TotalSeconds < 10)

{

Console.WriteLine("[MAIN THREAD]: Doing some work.");

// Pause to simulate a time-consuming operation. Thread.Sleep(TimeSpan.FromMilliseconds(100));

}

Console.WriteLine("[MAIN THREAD]: Complete.");

Console.ReadLine();

// Remove the test file. File.Delete("test.txt");

}

}

}

The following is an example of the output you will see when you run this test.

[MAIN THREAD]: Doing some work.

[ASYNC READER]: Read one block. [ASYNC READER]: Read one block.

[MAIN THREAD]: Doing some work.

[ASYNC READER]: Read one block. [ASYNC READER]: Read one block. [ASYNC READER]: Read one block. [ASYNC READER]: Read one block.

[MAIN THREAD]: Doing some work.

[ASYNC READER]: Read one block. [ASYNC READER]: Read one block. [ASYNC READER]: Read one block.

. . .

C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

163

5-10. Find Files That Match a Wildcard Expression

Problem

You need to process multiple files based on a filter expression (such as *.dll or mysheet20??.xls).

Solution

Use the overloaded version of the System.IO.DirectoryInfo.GetFiles method that accepts a filter expression and returns an array of FileInfo objects. For searching recursively across all subdirectories, use the overloaded version that accepts the SearchOption enumeration.

How It Works

The DirectoryInfo and Directory objects both provide a way to search the directories for files that match a specific filter expression. These search expressions can use the standard ? and * wildcards. You can use a similar technique to retrieve directories that match a specified search pattern by using the overloaded DirectoryInfo.GetDirectories method. You can also use the new overload of GetFiles for searching recursively using the SearchOption.AllDirectories enumeration constant.

The Code

The following example retrieves the names of all the files in a specified directory that match a specified filter string. The directory and filter expression are submitted as command-line arguments. The code then iterates through the retrieved FileInfo collection of matching files and displays the name and size of each one.

using System; using System.IO;

namespace Apress.VisualCSharpRecipes.Chapter05

{

static class Recipe05_10

{

static void Main(string[] args)

{

if (args.Length != 2)

{

Console.WriteLine(

"USAGE: Recipe05_10 [directory] [filterExpression]"); return;

}

DirectoryInfo dir = new DirectoryInfo(args[0]);

FileInfo[] files = dir.GetFiles(args[1]);

// Display the name of all the files. foreach (FileInfo file in files)

{

Console.Write("Name: " + file.Name + " "); Console.WriteLine("Size: " + file.Length.ToString());

}

164 C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

5-11. Test Two Files for Equality

Problem

You need to quickly compare the content of two files and determine if it matches exactly.

Solution

Calculate the hash code of each file using the System.Security.Cryptography.HashAlgorithm class, and then compare the hash codes.

How It Works

You might compare file contents in a number of ways. For example, you could examine a portion of the file for similar data, or you could read through each file byte by byte, comparing each byte as you go. Both of these approaches are valid, but in some cases, it’s more convenient to use a hash code algorithm.

A hash code algorithm generates a small (typically about 20 bytes) binary fingerprint for a file. While it’s possible for different files to generate the same hash codes, that is statistically unlikely to occur. In fact, even a minor change (for example, modifying a single bit in the source file) has an approximately 50-percent chance of independently changing each bit in the hash code. For this reason, hash codes are often used in security code to detect data tampering. (Hash codes are discussed in more detail in recipes 11-14, 11-15, and 11-16.)

To create a hash code, you must first create a HashAlgorithm object, typically by calling the static HashAlgorithm.Create method. You can then call HashAlgorithm.ComputeHash, which returns a byte array with the hash data.

The Code

The following example demonstrates a simple console application that reads two filenames that are supplied as arguments and uses hash codes to test the files for equality. The hashes are compared by converting them into strings. Alternatively, you could compare them by iterating over the byte array and comparing each value. This approach would be slightly faster, but because the overhead of converting 20 bytes into a string is minimal, it’s not required.

using System; using System.IO;

using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter05

{

static class Recipe05_11

{

static void Main(string[] args)

C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

165

{

if (args.Length != 2)

{

Console.WriteLine("USAGE: Recipe05_11 [fileName] [fileName]"); return;

}

Console.WriteLine("Comparing " + args[0] + " and " + args[1]);

// Create the hashing object.

using (HashAlgorithm hashAlg = HashAlgorithm.Create())

{

using (FileStream fsA = new FileStream(args[0], FileMode.Open), fsB = new FileStream(args[1], FileMode.Open))

{

// Calculate the hash for the files.

byte[] hashBytesA = hashAlg.ComputeHash(fsA); byte[] hashBytesB = hashAlg.ComputeHash(fsB);

// Compare the hashes.

if (BitConverter.ToString(hashBytesA) == BitConverter.ToString(hashBytesB))

{

Console.WriteLine("Files match.");

}

else

{

Console.WriteLine("No match.");

}

}

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

5-12. Manipulate Strings Representing Filenames

Problem

You want to retrieve a portion of a path or verify that a file path is in a normal (standardized) form.

Solution

Process the path using the System.IO.Path class. You can use Path.GetFileName to retrieve a filename from a path, Path.ChangeExtension to modify the extension portion of a path string, and Path.Combine to create a fully qualified path without worrying about whether or not your directory includes a trailing directory separation (\) character.

166 C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

How It Works

File paths are often difficult to work with in code because of the many different ways to represent the same directory. For example, you might use an absolute path (C:\Temp), a UNC path (\\MyServer\\MyShare\temp), or one of many possible relative paths (C:\Temp\MyFiles\..\ or C:\Temp\MyFiles\..\..\temp).

The easiest way to handle file system paths is to use the static methods of the Path class to make sure you have the information you expect. For example, here is how you take a filename that might include a qualified path and extract just the filename:

string filename = @"..\System\MyFile.txt"; filename = Path.GetFileName(filename);

// Now filename = "MyFile.txt"

And here is how you might append the filename to a directory path using the Path.Combine method:

string filename = @"..\..\myfile.txt"; string fullPath = @"c:\Temp";

string filename = Path.GetFileName(filename); string fullPath = Path.Combine(fullPath, filename);

// (fullPath is now "c:\Temp\myfile.txt")

The advantage of this approach is that a trailing backslash (\) is automatically added to the path name if required. The Path class also provides the following useful methods for manipulating path information:

ChangeExtension modifies the current extension of the file in a string. If no extension is specified, the current extension will be removed.

GetDirectoryName returns all the directory information, which is the text between the first and last directory separators (\).

GetFileNameWithoutExtension is similar to GetFileName, but it omits the extension.

GetFullPath has no effect on an absolute path, and it changes a relative path into an absolute path using the current directory. For example, if C:\Temp\ is the current directory, calling GetFullPath on a filename such as test.txt returns C:\Temp\test.txt.

GetPathRoot retrieves a string with the root (for example, “C:\”), provided that information is in the string. For a relative path, it returns a null reference.

HasExtension returns true if the path ends with an extension.

IsPathRooted returns true if the path is an absolute path and false if it’s a relative path.

Note In most cases, an exception will be thrown if you try to supply an invalid path to one of these methods (for example, paths that include illegal characters). However, path names that are invalid because they contain a wildcard character (* or ?) will not cause the methods to throw an exception. You could use the Path.GetInvalidPathChars method to obtain an array of characters that are illegal in path names.

C H A P T E R 5 F I L E S, D I R E C TO R I E S, A N D I / O

167

5-13. Determine If a Path Is a Directory or a File

Problem

You have a path (in the form of a string), and you want to determine whether it corresponds to a directory or a file.

Solution

Test the path with the Directory.Exists and the File.Exists methods.

How It Works

The System.IO.Directory and System.IO.File classes both provide an Exists method. The

Directory.Exists method returns true if a supplied relative or absolute path corresponds to an existing directory, even a shared folder with an UNC name. File.Exists returns true if the path corresponds to an existing file.

The Code

The following example demonstrates how you can quickly determine if a path corresponds to a file or directory.

using System; using System.IO;

namespace Apress.VisualCSharpRecipes.Chapter05

{

static class Recipe05_13

{

static void Main(string[] args)

{

foreach (string arg in args)

{

Console.Write(arg);

if (Directory.Exists(arg))

{

Console.WriteLine(" is a directory");

}

else if (File.Exists(arg))

{

Console.WriteLine(" is a file");

}

else

{

Console.WriteLine(" does not exist");

}

}

Соседние файлы в предмете Программирование на C++