The .NET Framework Overview: Input/ Output in C#

The .NET CLR provides I/O functions in the System.IO namespace. This namespace contains classes for doing I/O and for other I/O-related functions, such as directory traversal, file watching, and so on.

Reading and writing happens using the Stream class, which merely describes how bytes can be read and written to some sort of backing store. Stream is an abstract class, so in practice classes derived from Stream will be used. The classes listed in Table 34-5 are available.

With the exception of BufferedStream, GZipStream, and DeflateStream, which sit on top of another stream, each stream defines where the written data will go.

The Stream class provides raw functions to read and write at a byte level, both synchronously and asynchronously. Usually, however, it’s nice to have a higher-level interface on top of a stream, and you can select from several supplied ones depending on what final format you want.

1. Binary

The BinaryReader and BinaryWriter classes read and write values in binary (or raw) format. For example, a BinaryWriter can write an int, followed by a float, followed by another int. These classes operate on a stream.

2. Text

The TextReader and T extWriter abstract classes define how text is read and written. They allow operations on characters, lines, blocks, and so on. Two implementations of TextReader are available.

The somewhat strangely named StreamWriter class is the one used for “normal” I/O (opening a file and reading the lines out) and operates on a Stream.

The StringReader and StringWriter classes can read and write from a string.

3. XML

The XmlTextReader and XmlTextWriter classes read and write XML. They’re similar to TextReader and TextWriter in design, but they don’t derive from those classes because they deal with XML entities rather than text. They’re low-level classes used to create or decode XML from scratch.

3.1. Serial Ports

New in the .NET Framework 2.0 is support for serial ports. The types that implement serial port support are contained in the System.IO. Ports namespace, and they support operations that were previously available only through interop. The main type is SerialPort, which represents a physical serial port and allows various properties such as baud rate, parity, and timeouts to be set. SerialPort has methods that provide direct access to the data that’s flowing through the port and also supports stream-based access so you can use helper streams such as BufferedStream or asynchronous operations.

This sample shows both the direct and the stream-based approach:

using System.IO.Ports;

byte[] buffer = new byte[256];

using (SerialPort sp = new SerialPort(“COM1”, 19200))

{

sp.Open();

//read directly

sp.Read(buffer, 0, (int)buffer.Length);

//read using a Stream

sp.BaseStream.Read(buffer, 0, (int)buffer.Length);

}

4. Reading and Writing Files

You have two ways to get streams that connect to files. The first is to use the FileStream class, which provides full control over file access, including access mode, sharing, and buffering:

using System; using System.IO;

class Test {

public static void Main()

{

FileStream f = new FileStream(“output.txt”, FileMode.Create);

StreamWriter s = new StreamWriter(f);

s.WriteLine(“{0} {1}”, “test”, 55);

s.Close();

f.Close();

}

}

The second way is to use the functions in the File class to get a stream to a file. This is most useful if a File object with the file information is already available, as in the PrintFile() function in the next example.

5. Traversing Directories

This example shows how to traverse a directory structure. It defines both a DirectoryWalker class that takes delegates to be called for each directory and file and a path to traverse:

using System; using System.IO;

public class DirectoryWalker {

public delegate void ProcessDirCallback(DirectoryInfo dir, int level, object obj);

public delegate void ProcessFileCallback(FileInfo file, int level, object obj);

public DirectoryWalker( ProcessDirCallback dirCallback,

ProcessFileCallback fileCallback)

{

this.dirCallback = dirCallback;

this.fileCallback = fileCallback;

}

public void Walk(string rootDir, object obj)

{

DoWalk(new DirectoryInfo(rootDir), 0, obj);

}

void DoWalk(DirectoryInfo dir, int level, object obj)

{

foreach (FileInfo f in dir.GetFiles())

{

if (fileCallback != null)

fileCallback(f, level, obj);

}

foreach (DirectoryInfo d in dir.GetDirectories())

{

if (dirCallback != null)

dirCallback(d, level, obj);

DoWalk(d, level + 1, obj);

}

}

ProcessDirCallback dirCallback;

ProcessFileCallback fileCallback;

}

class Test {

public static void PrintDir(DirectoryInfo d, int level, object obj)

{

WriteSpaces(level * 2);

Console.WriteLine(“Dir: {0}”, d.FullName);

}

public static void PrintFile(FileInfo f, int level, object obj)

{

WriteSpaces(level * 2);

Console.WriteLine(“File: {0}”, f.FullName);

}

public static void WriteSpaces(int spaces)

{

for (int i = 0; i < spaces; i++)

Console.Write(” “);

}

public static void Main(string[] args)

{

DirectoryWalker dw = new DirectoryWalker(

new DirectoryWalker.ProcessDirCallback(PrintDir),

new DirectoryWalker.ProcessFileCallback(PrintFile));

string root = “.”;

if (args.Length == 1) root = args[0];

dw.Walk(root, “Passed string object”);

}

}

6. Starting Processes

The .NET Framework provides the Process class, which starts processes. The following example shows how to start Notepad:

// file=process.cs

// compile with csc process.cs using System.Diagnostics;

class Test {

public static void Main()

{

ProcessStartInfo startInfo = new ProcessStartInfo();

startInfo.FileName = “notepad.exe”;

startInfo.Arguments = “process.cs”;

Process.Start(startInfo);

}

}

The arguments used in starting the process are contained in the ProcessStartInfo object.

Redirecting Process Output

Sometimes it’s useful to get the output from a process. You can do this in the following way:

using System;

using System.Diagnostics;

class Test {

public static void Main()

{

Process p = new Process();

p.StartInfo.FileName = “cmd.exe”;

p.StartInfo.Arguments = “/c dir *.cs”;

p.StartInfo.UseShellExecute = false;

p.StartInfo.RedirectStandardOutput = true; p.Start();

string output = p.StandardOutput.ReadToEnd();

Console.WriteLine(“Output:”);

Console.WriteLine(output);

}

Detecting Process Completion

It’s also possible to detect when a process exits:

// file=process3.cs

// compile with csc process3.cs

using System;

using System.Diagnostics;

class Test {

static void ProcessDone(object sender, EventArgs e)

{

Console.WriteLine(“Process Exited”);

}

public static void Main()

{

Process p = new Process();

p.StartInfo.FileName = “notepad.exe”;

p.StartInfo.Arguments = “process3.cs”;

p.EnableRaisingEvents = true;

p.Exited += new EventHandler(ProcessDone);

p.Start();

p.WaitForExit();

Console.WriteLine(“Back from WaitForExit()”);

}

}

This example shows two ways of detecting process completion. The ProcessDone () function is called when the Exited event is fired, and the WaitForExit() function also returns when the process is done.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *