Microsoft & .NETVisual C#File I/O with Streams - Part 1 - An Excerpt from Inside...

File I/O with Streams – Part 1 – An Excerpt from Inside C#, Second Edition

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

From:
Inside Visual C#, Second Edition
By Tom Archer
ISBN: 0735616485
Published by Microsoft Press

Introduction

This chapter divides neatly into two main topics.
First, we.ll consider the classes provided by the .NET Framework classes,
which meet the lower-level data transfer requirements of the streams-based
I/O framework. These classes further divide into stream classes and file system classes.that is, classes that actually represent data streams, and classes that represent file system objects such as files and directories. Then we.ll look at how you can enhance any custom class to allow it
to fit seamlessly into the standard I/O framework. This enhancement is based on a standard attribute that marks your class as capable of being serialized. The serialization process is used in conjunction with the streams classes to stream your custom class objects from one place to another.in memory, to a remote location, or to persistent storage.
As part of our exploration of the streams framework, we.ll consider the different types of stream, types of file system objects, and potential application environments, including Microsoft Windows.based and Web-based environments.

Stream Classes

The .NET Framework classes offer a streams-based I/O framework, with the core classes in the System.IO namespace. All classes that represent streams inherit from the Stream class, and the key classes are listed in Table 1.


Table 1 – String and WriteLine Format Specifiers

























































Class



Description



Stream



The abstract base class Stream supports reading and writing bytes.



FileStream



In addition to basic Stream behavior, this class supports random access to files through its Seek method and supports both synchronous and asynchronous operation.



MemoryStream



A nonbuffered stream whose encapsulated data is directly accessible in memory. This stream has no backing store and might be useful as a temporary buffer.



BufferedStream



A Stream that adds buffering to another Stream, such as a NetworkStream. (FileStream already has buffering internally, and a MemoryStream doesn’t need buffering.) A BufferedStream object can be composed around some types of streams to improve read and write performance.



TextReader



The abstract base class for StreamReader and StringReader objects. While the implementations of the abstract Stream class are designed for byte input and output, the implementations of TextReader are designed for Unicode character output.



StreamReader



Reads characters from a Stream, using Encoding to convert characters to and from bytes.



StringReader



Reads characters from a String. StringReader allows you to treat a String with the same API; thus, your output can be either a Stream in any encoding or a String.



TextWriter



The abstract base class for StreamWriter and StringWriter objects. While the implementations of the abstract Stream class are designed for byte input and output, the implementations of TextWriter are designed for Unicode character input.



StreamWriter



Writes characters to a Stream, using Encoding to convert characters to bytes.



StringWriter



Writes characters to a String. StringWriter allows you to treat a String with the same API; thus, your output can be either a Stream in any encoding or a String.



BinaryReader



Reads binary data from a stream.



BinaryWriter



Writes binary data to a stream.



Two classes derived from Stream but not listed in Table 1 are offered in other namespaces. The
NetworkStream class represents a Stream over a network connection and resides in the
System.Net.Sockets namespace, and the CryptoStream class links data streams to cryptographic
transformations and resides in the System.Security.Cryptography namespace.

The design of the Stream class and its derivatives is intended to provide a generic view of data sources and destinations so that the developer can interchangeably use any of these classes without redesigning the application. In general, Stream objects are capable of one or more of the following:

  • Reading The transfer of data from a stream into a
    data structure, such as an array of bytes

  • Writing The transfer of data from a data structure
    into a stream

  • Seeking The querying and modifying of the current position within a stream

Note that a given stream might not support all these features. For example, NetworkStream objects don’t support seeking. You can use the CanRead, CanWrite, and CanSeek properties of Stream and its derived classes to determine precisely which operations a given stream does in fact support.

The FileStream Class

Let.s dive into some code. Consider this simple use of FileStream, which creates a file, writes out an array of bytes, and closes the file. The application then opens the file again, tests that the stream supports reading, and reads in the bytes one by one, converting each byte to a character and appending it to a string:

using System.IO;

public class StreamsIOApp
{
  public static void Main(string[] args)
  {
    byte[] buf1 = new Byte[]
      {76,101,116,32,116,104,101,114,101,
       32,98,101,32,108,105,103,104,116};

    Stream s = new FileStream("Foo.txt", FileMode.Create);
    s.Write(buf1, 0, buf1.Length);
    s.Close();

    s = new FileStream("Foo.txt", FileMode.Open);
    int i;
    string str = "";
    if (s.CanRead)
    {
      for (i = 0; (i = s.ReadByte()) != -1; i++)
      {
        str += (char)i;
      }
    }
    s.Close();
    Console.WriteLine(str);
  }
}

Here’s the output:

Let there be light

Note that we.re using only the Stream (virtual) methods, so we can safely use a Stream
reference to the derived FileStream object. Of course, we also could’ve written the code to
use a FileStream reference:

//Stream s = new FileStream(
FileStream s = new FileStream(

It.s no coincidence that the streams methods tend to use bytes and byte arrays.you can visualize a stream as a byte array that might be attached to some memory buffer or to some disk file or device. Streams use the concept of an internal stream pointer: when you open the stream, the stream pointer is normally positioned at the first byte of the stream. Most streams support seeking.the ability to move the internal stream pointer
to an arbitrary position. Therefore, instead of the create-write-close-open-read-close pattern, we can avoid closing and opening when we want to switch between writing and reading by seeking in between.

For example, the following continuation code reopens the same file, reports on its length and the position of the internal stream pointer, and then tests to see whether the stream supports seeking. If the stream does, we seek 13 bytes from one of the three relative starting points: SeekOrigin.Begin, SeekOrigin.Current, or SeekOrigin.End. If we write to the stream at that point, we.ll overwrite some of the data that was originally in the stream. We can then seek again.rather than closing and opening.before reading from the file:

byte[] buf2 = new Byte[]
  {97,112,112,108,101,115,97,117,99,101};
s = new FileStream("Foo.txt", FileMode.Open);
Console.WriteLine("Length: {0}, Position: {1}", s.Length, s.Position);
if (s.CanSeek)
{
  s.Seek(13, SeekOrigin.Begin);
  Console.WriteLine("Position: {0}", s.Position);
  s.Write(buf2, 0, buf2.Length);
}

str = "";
s.Seek(0, SeekOrigin.Begin);
for (i = 0; (i = s.ReadByte()) != -1; i++)
{
  str += (char)i;
}
Console.WriteLine(str);

Here’s the output:

Length: 18, Position: 0
Position: 13
Let there be applesauce

Note that if you want to Seek from SeekOrigin.End, you should supply a negative value.
However, if you want to Seek from SeekOrigin.Current, you can supply either a positive or
negative value depending on which direction you want to go. In addition to using the Length
and Position properties, you could even arbitrarily set the length by using Stream.SetLength.

str = "";
s.SetLength(s.Length - 4);
s.Seek(0, SeekOrigin.Begin);
for (i = 0; (i = s.ReadByte()) != -1; i++)
{
  str += (char)i;
}
s.Close();
Console.WriteLine(str);

This is the output:

Let there be apples

A stream must support both writing and seeking for SetLength to work.

Instead of using Seek, we could achieve the same effect by using
the Position property:

//s.Seek(0, SeekOrigin.Begin);
s.Position = 0;

Finally, we could append to the end of the file. If you open a stream
for appending, the internal stream pointer will be positioned at the end of
the stream, not the beginning, as is normally the case. FileMode.Append can
be used only in conjunction with FileAccess.Write.any attempt to read from the
file will fail and throw an ArgumentException. Hence, once we.ve opened the file
for appending and have written out some more data, we must close it and reopen it
before reading from it:

byte[] buf4 = new Byte[]
  {32,97,110,100,32,112,101,97,114,115};
s = new FileStream("Foo.txt", FileMode.Append, FileAccess.Write);
s.Write(buf4, 0, buf4.Length);
s.Close();

s = new FileStream("Foo.txt", FileMode.Open);
str = "";
for (i = 0; (i = s.ReadByte()) != -1; i++)
{
  str += (char)i;
}
Console.WriteLine(str);
s.Close();

This is the output:

Let there be apples and pears

If you construct a FileStream object with FileMode.Append and don.t specify
the access permissions, the object will be constructed with read permission.
Therefore, the following statements are equivalent:

s = new FileStream(
//  "Foo.txt", FileMode.Append, FileAccess.Write);
    "Foo.txt", FileMode.Append);

As a general rule, you should always protect your file-handling operations
with the appropriate exception-handling code. In this article, we’ll keep
this code to a minimum. However, the subject of exception handling is covered
in much more detail in Chapter 12, “Error Handling with Exceptions” of my
Inside C# book.

The StreamReader and StreamWriter Classes

As you can see, FileStream is OK for reading and writing raw byte
(binary) data. If you want to work with character data, classes such as
StreamReader and StreamWriter are more suitable. These classes will use a
FileStream object in the background, effectively interposing a
character-interpolation layer on top of the raw byte processing.
Closing the StreamReader/StreamWriter also closes the underlying
FileStream:

public class ReadWriteApp
{
  public static void Main(string[] args)
  {
    FileStream s = new FileStream("Bar.txt", FileMode.Create);
    StreamWriter w = new StreamWriter(s);
    w.Write("Hello World");
    w.Close();

    s = new FileStream("Bar.txt", FileMode.Open);
    StreamReader r = new StreamReader(s);
    string t;
    while ((t = r.ReadLine()) != null)
    {
      Console.WriteLine(t);
    }
    w.Close();
  }
}

The output follows:

Hello World

Recall from Table 1 that the StreamReader and StreamWriter classes can
use Encoding to convert characters to bytes and vice versa. To write our
data to the file with some encoding other than the default, we need to
construct the StreamWriter and StreamReader with an Encoding parameter,
as shown here:

//StreamWriter w = new StreamWriter(s);
StreamWriter w = new StreamWriter(s, System.Text.Encoding.BigEndianUnicode);

Please note that encoding is discussed in more detail in Chapter 10, “String Handling and
Regular Expressions.” of
my book.

If you want to open a file that’s been set to read-only, you need to pass
an additional parameter to the FileStream constructor to specify that you
only want to read from the file:

s = new FileStream("../../TextFile1.txt", FileMode.Open, FileAccess.Read);

Paths in C#

Because C# treats the backslash the same way as C and C++ do, if you
want to specify a path for a file, you have three choices. You can
either use double backslashes, as in:

s = new FileStream("C:tempGoo.txt", FileMode.Create);

…or use forward (Unix-style) slashes:

s = new FileStream("C:/temp/Goo.txt", FileMode.Create);

…or use the “@” character, which is a control-character suppressor:

s = new FileStream(@"C:tempGoo.txt", FileMode.Create);

Memory and Buffered Streams

The classes MemoryStream and BufferedStream are both derived from the
abstract Stream class, just as FileStream is. Therefore, MemoryStream
and BufferedStream share many of the same characteristics and functionality. Both are designed for streaming data into and out of memory rather than persistent storage. Both can be associated with a stream of another kind.such as a file.if required, and thus both can be used to act as a buffer between memory and persistent storage. The MemoryStream class offers methods such as WriteTo, which will write to another stream. Similarly, a BufferedStream object is normally associated with another stream on construction, and when you close the BufferedStream, its contents are flushed to the associated stream. In the following example, we first create a MemoryStream object with an initial capacity of 64 bytes and print some arbitrary property values. Then we write out 64 bytes and report the same properties. We then use MemoryStream.GetBuffer to get a byte array of the entire contents of the stream and print the values, before finally closing the stream:

class MemStreamApp
{
  static void Main(string[] args)
  {
    MemoryStream m = new MemoryStream(64);
    Console.WriteLine("Length: {0}tPosition: {1}tCapacity: {2}",
                      m.Length, m.Position, m.Capacity);

    for (int i = 0; i < 64; i++)
    {
      m.WriteByte((byte)i);
    }
    Console.WriteLine("Length: {0}tPosition: {1}tCapacity: {2}",
                      m.Length, m.Position, m.Capacity);

    Console.WriteLine("nContents:");
    byte[] ba = m.GetBuffer();
    foreach (byte b in ba)
    {
      Console.Write("{0,-3}", b);
    }

    m.Close();
  }
}

Here's the output:

Length: 0       Position: 0     Capacity: 64
Length: 64      Position: 64    Capacity: 64

Contents:
0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
60 61 62 63

Given that the stream is already up to capacity, let's see what
happens if we write some more data to it. Will it generate a runtime
exception? Will it overrun into unallocated memory? Or will it just
dynamically resize the buffer? The last scenario is, of course, the correct
one.which you.ll realize if you've examined the previous output closely.
Although we set an initial capacity of 64, this wasn.t the initial size of
the memory buffer because the buffer was initialized to 0. So, clearly, the
buffer is dynamically resized automatically.

string s = "Foo";
for (int i = 0; i < 3; i++)
{
  m.WriteByte((byte)s[i]);
}
Console.WriteLine("nLength: {0}tPosition: {1}tCapacity: {2}",
                  m.Length, 
                  m.Position, 
                  m.Capacity);

Here's the output:

Length: 67      Position: 67    Capacity: 256

As you can see, the minimum block size is 256 bytes.

Finally, let.s associate this MemoryStream with a FileStream before closing it:

FileStream fs = new FileStream("Goo.txt", 
                                FileMode.Create, 
                                FileAccess.Write);
m.WriteTo(fs);
m.Close();

Now let.s do the same thing with a BufferedStream object. Although
generally similar, there are some subtle differences between MemoryStream
and BufferedStream. First, we can construct a BufferedStream only by
initializing it to some other existing Stream.in this example, a file. The
buffer in a BufferedStream is managed differently than that in the MemoryStream: if we don.t specify an initial size when we construct the object, it defaults to 4096 bytes. Also, if we've written some data to the BufferedStream and want to read it back, we must use the Read method.because there is no GetBuffer method. Using the Read method means making sure the internal stream pointer is positioned correctly.in this case, at the beginning of the stream.before attempting the read:

class BufStreamApp
{
  static void Main(string[] args)
  {
    // Create a FileStream for the BufferedStream.
    FileStream fs = new FileStream("Hoo.txt",
        FileMode.Create, FileAccess.ReadWrite);
    BufferedStream bs = new BufferedStream(fs);
    Console.WriteLine("Length: {0}tPosition: {1}",
                      bs.Length, bs.Position);

    for (int i = 0; i < 64; i++)
    {
      bs.WriteByte((byte)i);
    }
    Console.WriteLine("Length: {0}tPosition: {1}",
                      bs.Length, bs.Position);

    // Reset to the beginning and read the data.
    Console.WriteLine("nContents:");
    byte[] ba = new byte[bs.Length];
    bs.Position = 0;
    bs.Read(ba, 0, (int)bs.Length);
    foreach (byte b in ba)
    {
      Console.Write("{0,-3}", b);
    }

    // Write some more, exceeding capacity.
    string s = "Foo";
    for (int i = 0; i < 3; i++)
    {
      bs.WriteByte((byte)s[i]);
    }
    Console.WriteLine("nLength: {0}tPosition: {1}t",
                      bs.Length, bs.Position);

    for (int i = 0; i < (256-67)+1; i++)
    {
      bs.WriteByte((byte)i);
    }
    Console.WriteLine("nLength: {0}tPosition: {1}t",
                      bs.Length, bs.Position);

    bs.Close();
  }
}

The console output is listed here:

Length: 0       Position: 0
Length: 64      Position: 64

Contents:
0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
60 61 62 63

Length: 67      Position: 67

Note that the Hoo.txt file will have the same contents as those in the
MemoryStream example. We didn.t have to explicitly write from the
BufferedStream to the FileStream as we did for MemoryStream because the
BufferedStream must be associated with another stream in the first place.
All writes to the BufferedStream will be buffered for writing to the
associated stream, and merely closing the BufferedStream object is enough
to flush any pending writes to the file.

String Readers and Writers

Objects of the String class are immutable but that you can process mutable objects of the StringBuilder class. The functionality offered by the StringReader and StringWriter classes overlaps somewhat with that of both the MemoryStream and BufferedStream classes and the String and StringBuilder classes. You can use StringWriter to build a mutable string in memory and extract from it either a String or a StringBuilder
class. StringWriter has a Write and a WriteLine method that function almost
identically to those in the Console class, and you can build the internal string in a StringWriter by using any technique that works with a raw string. For example:

class StringReadWriteApp
{
  static void Main(string[] args)
  {
    StringWriter w = new StringWriter();
    w.WriteLine("Sing a song of {0} pence", 6);
    string s = "A pocket full of rye";
    w.Write(s);
    w.Write(w.NewLine);
    w.Write(String.Format(4 +" and " +20 +" blackbirds"));
    w.Write(new StringBuilder(" baked in a pie"));
    w.WriteLine();
    w.Close();
    Console.WriteLine(w);
  }
}

Here's the output:

Sing a song of 6 pence
A pocket full of rye
4 and 20 blackbirds baked in a pie

Clearly, when we pass the StringWriter reference to Console.WriteLine,
the ToString method is being invoked. Note the call to StringWriter.Close
in the preceding code. Although there's a Flush method in the StringWriter
class, flushing the stream won't flush its underlying encoder unless you
explicitly call Close. Closing the stream will automatically flush it and
will ready it for destruction by calling Dispose. It.s also instructive to
step through this code in the debugger, where you.ll clearly see that the
StringWriter contains an internal StringBuilder field named _sb, which in turn contains a String field named m_StringValue. Figure 1 shows these fields.

Figure 2 - A binary file in the binary/hex editor. (click for larger image)

Now it becomes clearer. The string .Hello, World. is easy to see,
followed by 0x20, which is the space; then the decimal 123, which is
0x7B (the character .{.); followed by another space; then 8 bytes of
floating-point value. As you can see, the value of the very first byte
in the stream is 0x0C, or decimal 12. Anyone with any experience using
strings in Microsoft Visual Basic or using BSTRs in COM development will
recognize this right away. It.s the length of the following string.including
the space at the end.

Naturally, if you plan to write binary data, you.ll want to read back
binary data by using the BinaryReader class, as illustrated in the following
code. When you do so, remember to keep the Close calls at the end.

b.BaseStream.Position = 0;
BinaryReader r = new BinaryReader(t);
int i = 0;
while (true)
{
  i = b.BaseStream.ReadByte();
  if (-1 == i)
  {
    Console.WriteLine();
    break;
  }
  Console.Write("{0,-4}", i);
}

r.Close();
b.Close();
t.Close();

Here's the output from this revised application:

12  72  101 108 108 111 32  87  111 114 108 100 32  
123 0   0   0   32  246 40  92  143 194 213 70  64

File System Classes

In addition to the Stream class and its various derivatives, the
.NET Framework classes also offer a set of file system.related classes
for encapsulating information and functionality that's suitable for
processing files and directories. These classes reside in the System.IO
namespace and are listed in Table 2.


Table 2 - File system classes in the .NET Framework Classes

































Class



Description



FileSystemInfo



The abstract base class for FileInfo and DirectoryInfo objects, this class contains methods that are common to both file and directory manipulation. Useful when processing a lot of files and directories.



DirectoryInfo



Supports creation, deletion, and manipulation of directories (only instance methods).



FileInfo



Supports creation, deletion, and manipulation of files (only instance methods).



Directory



Supports creation, deletion, and manipulation of directories. (All methods are static.)



File



Supports creation, deletion, and manipulation of files. (All methods are static.)



Path



Performs operations on a String that contains file or directory path information.the file or directory doesn't need to exist.



Directory and DirectoryInfo

The FileInfo and DirectoryInfo classes encapsulate information about
files and directories as well as methods, such as Create, Delete, Open,
MoveTo, and CopyTo. These classes two also offer behavior similar to that
of CFile in the Microsoft Foundation Classes (MFC) and fstream in the
Standard C++ library. The following example gets a DirectoryInfo object
from the static Directory.GetCurrentDirectory method and then calls the
DirectoryInfo.GetFiles instance method to get a collection of FileInfo
objects. Finally, we iterate this collection to report on some arbitrary
properties of each file:

public class DirInfoApp
{
  public static void Main(string[] args)
  {
    DirectoryInfo dir = 
      new DirectoryInfo(Directory.GetCurrentDirectory());
    Console.WriteLine("Current Dir: {0}", dir.FullName);

    foreach (FileInfo f in dir.GetFiles())
    {
      Console.WriteLine("{0,-14}{1,10}{2,20}",
                        f.Name, f.Length, 
                        f.LastWriteTime);
    }
  }
}

Here's the output:

Current Dir: C:InsideCsharpChap11DirInfoDirInfobinDebug
DirInfo.exe         6144 04/01/2002 21:06:30
DirInfo.pdb        13824 04/01/2002 21:06:30

In addition to using GetCurrentDirectory, we can construct DirectoryInfo
objects by using a string for the desired path:

dir = new DirectoryInfo(".");
dir = new DirectoryInfo(@"C:Winnt");

The first example, of course, will produce the same results as calling GetCurrentDirectory will. Another useful method is GetDirectories, which will return as DirectoryInfo objects a collection of subdirectories of the current directory. The common parentage of DirectoryInfo and FileInfo is clear from the following code:

Console.WriteLine("n{0,-32}{1}", "Name", "LastWriteTime");
foreach (DirectoryInfo d in dir.GetDirectories())
{
  Console.WriteLine(
  "{0,-32}{1}", d.Name, d.LastWriteTime);
}

This is the output:

Name                            LastWriteTime
$NtUninstallQ301625$            24/12/2001 17:37:43
A5W_DATA                        11/02/2001 15:18:25
addins                          25/11/2001 12:16:25
AppPatch                        24/12/2001 15:14:34
Assembly                        24/12/2001 15:23:47
Config                          25/11/2001 12:17:06
Connection Wizard               07/01/2000 11:27:04
CSC                             25/11/2001 17:56:56
Cursors                         24/12/2001 15:15:07
Debug                           07/01/2002 09:00:23
Downloaded Program Files        16/12/2001 11:15:14
Driver Cache                    07/01/2000 11:27:04
Fonts                           25/11/2001 15:17:08
Help                            24/12/2001 16:03:31
IIS Temporary Compressed Files  06/04/2001 10:46:27
...

The DirectoryInfo class offers a reasonable set of methods for
creating, deleting, moving, and so on. For instance, we could
create a new directory at some arbitrary location. (In the following
example, the new directory is created within the current directory.)
We could then create a subdirectory within the new directory, set and
then get some attributes, and finally delete both the subdirectory and
the newly created parent directory:

dir = new DirectoryInfo("Foo");
if (false == dir.Exists)
  dir.Create();

DirectoryInfo dis = dir.CreateSubdirectory("Bar");
dis.Attributes |= FileAttributes.Hidden | FileAttributes.Archive;

Console.WriteLine("{0,-10}{1,-10}{2}", 
                  dis.Name, dis.Parent, dis.Attributes);

dis.Delete(true);
dir.Delete(true);

The output follows:

Bar       Foo       Hidden, Directory, Archive

Recall that the library also offers the Directory class, which
exposes only static methods. Thus, in the previous code, we could've
used the static method Directory.Delete instead and achieved the
same results:

//dir.Delete(true);
Directory.Delete(dir.Name, true);

The set of methods offered by the Directory class more or less
parallels the instance methods in the DirectoryInfo class. The
same parallelism is true for the File and FileInfo classes. In
both cases, a couple of additional methods that aren't offered by
the parallel class exist. For instance, the Directory class offers
a GetLogicalDrives method:

string[] sa = Directory.GetLogicalDrives();
Console.WriteLine("Logical Drives:");
foreach (string s in sa)
{
  Console.Write("{0,-4}", s);
}

Here's the output:

Logical Drives:
A: C: D: E: F:

File and FileInfo

In addition to offering a Directory class with only static methods
and a DirectoryInfo class with only instance methods, the library
offers a File class with only static methods and a FileInfo class with
only instance methods. To explore these two classes, we.ll rewrite our
example from the "Stream Classes" section that used FileStream, StreamReader,
and StreamWriter.first using FileInfo, then using File.

In the first example that follows, we use a FileInfo object and the
FileInfo.Create method in place of the overloaded FileStream constructor.
Similarly, we later open the same file by using FileInfo.Open in place of
the FileStream constructor. When you open a file this way, you must supply
the open mode (Append, Create, CreateNew, Open, OpenOrCreate, or Truncate),
access permissions (Read, ReadWrite, or Write), and sharing permissions you
want to grant to other objects using this file (None, Read, ReadWrite, or
Write). As you can see, one advantage of using FileInfo instead of FileStream
in this situation is that we can reuse the same FileInfo object.which contains
property values such as the filename.and simply regenerate a FileStream object
opened in the manner we require:

class FileFileInfoApp
{
  static void Main(string[] args)
  {
//    Stream s = new FileStream("Bar.txt", FileMode.Create);
      FileInfo f = new FileInfo("Bar.txt");
      FileStream fs = f.Create();

      StreamWriter w = new StreamWriter(fs);
      w.Write("Hello World");
      w.Close();

//    s = new FileStream("Bar.txt", FileMode.Open);
      fs = f.Open(FileMode.Open, 
                  FileAccess.Read, 
                  FileShare.None);
      StreamReader r = new StreamReader(fs);
      string t;
      while ((t = r.ReadLine()) != null)
      {
        Console.WriteLine(t);
      }
      w.Close();
      fs.Close();
      f.Delete();
  }
}

The equivalent code using the File class static methods is this:

FileInfo f2 = new FileInfo("Bar.txt");
FileStream fs2 = File.Create("Bar.txt");

StreamWriter w2 = new StreamWriter(fs2);
w2.Write("Goodbye Mars");
w2.Close();

fs2 = File.Open("Bar.txt", 
                FileMode.Open, 
                FileAccess.Read, FileShare.None);
StreamReader r2 = new StreamReader(fs2);
while ((t = r2.ReadLine()) != null)
{
  Console.WriteLine(t);
}
w2.Close();
fs2.Close();
f2.Delete();

One advantage that the FileInfo and File classes have over the
FileStream class in this situation is the OpenText, CreateText,
and AppendText methods. These methods will return StreamReader or
StreamWriter objects; therefore, we can use them as a kind of
shortcut, bypassing the need for a FileStream object:

FileInfo f3 = new FileInfo("Bar.txt");
StreamWriter w3 = f3.CreateText();
w3.Write("Farewell Pluto");
w3.Close();

StreamReader r3 = f3.OpenText();
while ((t = r3.ReadLine()) != null)
{
  Console.WriteLine(t);
}
w3.Close();

Parsing Paths

The Path class is designed to enable you to easily perform common
operations such as determining whether a filename extension is part of
a path and combining two strings into one pathname. A path is a string
that provides the location of a file or directory: it doesn't necessarily
point to a location on disk. Most members of the Path class don't interact
with the file system and don't verify the existence of the file specified by
a path string. Path class members that modify a path string have no effect
on the names of files in the file system. On the other hand, some of the
members of the Path class do validate that a specified path string has the
correct form, and they throw an exception if the string contains characters
that aren't valid in path strings. All members of the Path class are static
and can therefore be called without having an instance of a path. A path can
contain absolute or relative location information. Absolute paths fully specify
a location: the file or directory can be uniquely identified, regardless of
the current location. Relative paths specify a partial location: the current
location is used as the starting point when locating a file this way.

The following code illustrates how you might use the Path class to report
on various parts of the string that represents the current directory and
the name of the current executing module:

using System.Diagnostics;

class TestPathApp
{
  static void Main(string[] args)
  {
    Process p = Process.GetCurrentProcess();
    ProcessModule pm = p.MainModule;
    string s = pm.ModuleName;

    Console.WriteLine(Path.GetFullPath(s));
    Console.WriteLine(Path.GetFileName(s));
    Console.WriteLine(Path.GetFileNameWithoutExtension(s));
    Console.WriteLine(Path.GetDirectoryName(
                       Directory.GetCurrentDirectory()));
    Console.WriteLine(Path.GetPathRoot(
                       Directory.GetCurrentDirectory()));
    Console.WriteLine(Path.GetTempPath());
    Console.WriteLine(Path.GetTempFileName());
  }
}

This is the output:

C:DataInsideCsharpChap11TestPathTestPathbinDebugTestPath.exe
TestPath.exe
TestPath
C:DataInsideCsharpChap11TestPathTestPathbin
C:
C:DOCUME~1andrewLOCALS~1Temp
C:DOCUME~1andrewLOCALS~1Temptmp115.tmp

Summary

The .NET Framework includes a streams-based I/O subframework that's
based on the Stream class and its specialized derivatives. Related
classes that support file-based streaming include classes to represent
files and directories, with an appropriate range of functionality. We.ve
seen how an application can use the streams pattern to stream data
between memory buffers, or to persistent storage, with a comprehensive
set of options for formatting and encoding the stream. The .NET Framework
classes supply two primary stream formatting classes to support both binary
and XML-formatted data, as well as a set of character encoding classes.
In the second part of this two-part series, we'll cover the topics of Non-console
use of streams and serialization.

Downloads



Download demo projects - 98 Kb

For more information:
Inside Visual C#, Second Edition
By Tom Archer
ISBN: 0735616485
Published by Microsoft Press

Reprinted with Permission.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories