C# I/O and Networking
by Raffi Krikorian07/17/2001
Arguably, the reason that Java has become so popular as a programming language is because of the way it has abstracted away the difficulties in performing input/output and networking operations. C# has taken the same approach and has provided libraries that hide these complications. The previous two articles in this series have focused around the different language structures that a Java programmer needs to know to build simple C# programs; this article is going to focus on a few C# namespaces dealing with I/O and networking, along with some common usage patterns of these libraries.
Understanding Streams
Streams in both Java and C# usually involve reading and writing bytes from and to the console, the filesystem, or the network. In both languages, the stream paradigm is used more generally whenever a program needs to move or operate on a group of bytes.
Java provides two abstract classes, the java.io.InputStream and the java.io.OutputStream, that contain the unimplemented methods that need to be authored to allow a program to read and write from these two stream sub-types. On the other hand, C# unifies these two classes into System.IO.Stream class; instead of having two objects, one for reading and one for writing, a C# Stream object needs to be tested for its capabilities via the CanRead and the CanWrite properties.
Synchronous I/O
Synchronous I/O is syntactically very similar in both languages. Both the Java java.io.InputStream and java.io.OutputStream, along with the C# System.IO.Stream, have methods to operate one byte at a time, along with methods that operate on an array of bytes. (C# lacks the syntactic sugar of operating on a whole array; it instead only knows how to use an array along with an offset/length pair.)
Table 1: Synchronous I/O methods in Java and C# | ||
Function |
Java |
C# |
Read one byte |
The |
The |
Read a whole byte array |
The |
No such syntactical method -- use the read method that has an offset/count pair |
Read into a portion of a byte array |
The |
The |
Write one byte |
The |
The |
Write an entire byte |
The |
No specific method -- use the method that requires an offset/count pair |
Write a portion of a byte array |
The |
The |
One piece of reminder advice for those Java programmers out there -- do not forget to catch IOException. Unlike Java, the C# compiler is not going to enforce exceptions at compile time.
|
|
Asynchronous I/O
What Java lacks is a formal way of performing I/O operations asynchronously; there is no "built-in" way to cause a read or a write to occur on a stream, and then check the result later on. The closest simulation in Java is to spawn a java.lang.Thread around a synchronous method and either have the Thread cause a side effect or perform a callback, either with the status of the I/O operation. C# has asynchronous I/O methods built into its libraries.
For example, to perform an asynchronous read( byte[] b ) call in Java, both with a callback and a state that can be checked afterward, a possible implementation could look somewhat like:
// variables to hold the side effects of the read
int read; // to hold the result of the read
IOException exception; // to hold a possible exception
Object wait ...
// some value to block on until the end of the = call
// a wrap around a read on the InputStream variable "is"
( new Thread( new Runnable() {
public void run() {
try {
read is.read();
} catch( IOException error ) {
exception error;
} finally {
synchronized( wait ) {
// wake up all other threads waiting on this
// read
wait.notifyAll();
}
// call a callback method
callback();
}
}
} ) ).start();
This will cause either the value of the read, or the exception that is caught when reading, to be stored in the "read" and "exception" respectively. Another thread may also wait on the variable "wait," or implement the method "callback" to know when the asynchronous read has completed.
In an attempt to clean all this up, C# provides the methodsBeginRead and EndRead that wrap up all the above functionality. The signature to BeginRead is similar to the signature of Read, except it takes two more variables -- an AsyncCallback and a state object -- and it returns an IAsyncResult object that can be used later to check on the progress of the asynchronous read. A standard use of BeginRead looks something like:
IAsyncResult iar sbs.BeginRead( buffer, 0, 1, new AsyncCallback( =
callback ), null );
with the callback method looking like
public void callback( IAsyncResult iar )
To see how many bytes have actually been read, the EndRead method call can be called with the IAsyncResult object. Be warned, however, that calling EndRead will block until the BeginRead completes -- to find out the state of the read without blocking, check the IsCompleted property on the IAsyncResult return. Also note that the contents of the buffer variable are not guaranteed until the asynchronous read has completed.
Implementing Streams
Java and C# streams are similar enough that, knowing what you know about Java streams, implementing a C# stream should not be that difficult. The main difference between implementing the two is not only that the appropriate reading or writing methods need be implemented, but also since a C# Stream class may be functioning either as a reader or a writer, the capability properties need to reflect exactly what the Stream can do.
Table 2: Implementing Streams in Java and C# | ||
Function |
Java |
C# |
reading |
Implement, at minimum, the |
Cause the |
seeking |
Have the skip method in |
Use the |
writing |
Implement the |
Return true from the |
The C# Stream class provides lots of options on what methods to implement for functionality. Overriding Read and Write (both taking a byte array, an offset, and a length) is usually enough, as the default implementations of all the methods make use of the other methods; simply overriding at least one of the read/write methods will add the needed functionality to the entire stream. The default implementation of ReadByte and WriteByte will convert the long value to and from a byte array, whereas the default implementation of the asynchronous BeginRead and BeginWrite methods will execute Read or Write in a separate thread.
Readers and Writers
Most of this article has been spent talking about the System.IO.Stream class in C#; however, some time needs to be devoted to talking about the System.IO.TextReader and the System.IO.TextWriter. These two classes most closely mimic the Java model of I/O, with one class type for reading while another type handles writing. Where the C# Stream object encapsulates knowledge on how to both read and write bytes, the TextReader and TextWriter classes encapsulate reading and writing characters respectively. The most commonly-used classes that derive from the two above are the System.IO.StreamReader and the System.IO.StreamWrtiter classes -- these two can take a Stream object and, optionally, a System.Text.Encoding object to specify how to convert the byte stream to a character stream (C# defaults to using a UTF-8 encoder/decoder).
If access to stream-like functionality is needed, and instead of working with bytes, you are programming for the use of characters, it may be easier to implement sub-classes of the TextReader and TextWriter classes than to deal with the nuances of the Stream class. Although if the Stream is implemented properly, then you can use the StreamReader and the StreamWriter classes to wrap your custom stream.
Pages: 1, 2 |

