7.3. StreamReader
In the sample code (first_file.cs
) of Writing Files from last section, you specified
a file sample.txt
, which currently exists in the present project folder.
The code is as seen below:
1 using System;
2 using System.IO;
3
4 namespace IntroCSCS
5 {
6 class Ch07File // basics of file writing
7 {
8 public static void Main()
9 {
10 StreamWriter writer = new StreamWriter("sample.txt");
11 writer.WriteLine("This program is writing");
12 writer.WriteLine("our first file.");
13 writer.Close();
14 }
15 }
16 }
Now, take a look at the following code (print_first_file.cs
) and compare to
the code above (first_file.cs
):
using System;
using System.IO;
namespace IntroCSCS
{
class PrintFirstFile // basics of reading file lines
{
public static void Main()
{
StreamReader reader = new StreamReader("sample.txt");
string line = reader.ReadLine(); // first line // string? ==> nullable
Console.WriteLine(line);
line = reader.ReadLine(); // second line
Console.WriteLine(line);
reader.Close();
}
}
}
Now you have read a file and used it in a program.
In the first line of Main
, the file (sample.txt
) is again associated
with a C# variable name (reader
), this time for reading as a StreamReader
,
instead of a StreamWriter
, object. A StreamReader
can only open an existing
file, so sample.txt
must already exist.
Again we have parallel names to those used with Console
, but in this case
the ReadLine
method returns the next line from the file. Here the string from
the file line is assigned to the variable line
. Each call the ReadLine reads the
next line of the file. Such behavior is similar to the Console.ReadLine()
that you
know about but different as StreamReader.ReadLine()
reads the next line from the
input stream
(or null if the end of the input stream is reached) rather than from
user command line input.
Finally, using the Close
method is generally optional with files being read.
There is nothing to lose if a program ends without closing a file that was being
read.
7.3.1. Reading to End of Stream
In first_file.cs
, we explicitly coded reading two lines. You will often
want to process each line in a file, without knowing the total number of
lines at the time when you were programming. This means that files provide us with our
second kind of a sequence
: the sequence of lines in the file!
To process all of them will require a loop and a new test to make sure that you
have not yet come to the end of the file’s stream: You can use the EndOfStream
property. It has the wrong sense (true at the end of the file), so we negate it,
testing for !reader.EndOfStream
to continue reading.
The example program print_file_lines.cs
below reads and prints the contents of
a file specified by the user, one line at a time:
using System;
using System.IO;
namespace IntroCSCS
{
class PrintFileLines // demo of using EndOfStream test
{
public static void Main()
{
string userFileName = UI.PromptLine("Enter name of file to print: ");
var reader = new StreamReader(userFileName);
while (!reader.EndOfStream) {
string line = reader.ReadLine();
Console.WriteLine(line);
}
reader.Close();
}
}
}
var
For conciseness (and variety) we declared
reader
using the more compact syntax withvar
:var reader = new StreamReader(userFileName);You can use
var
in place of a declared type to shorten your code with a couple of restrictions:
Use an initializer, from which the type of the variable can be inferred.
Declare a local variable inside a method body or in a loop heading.
Declare only a single variable in the statement.
We could have used this syntax long ago, but as the type names become longer, it is more useful!
You can run this program. You need an existing file to read. An obvious file is
the source file itself: print_file_lines.cs
. Or, if you have implemented/tested
the files by placing the code in your Program.cs
, you may use the sample.txt
file
here. In the latter case, you should see:
Enter name of file to print: sample.txt
This program is writing
our first file.
Things to note about reading from files:
Reading from a file returns the part read, of course. Never forget the side effect: The location in the file advances past the part just read. The next read does not return the same thing as last time. It returns the next part of the file.
Our
while
test conditions so far have been in a sense “backward looking”: We have tested a variable that has already been set. The test withEndOfStream
is forward looking: looking at what has not been processed yet. Other than making sure the file is opened, there is no variable that needs to be set before awhile
loop testing forEndOfStream
.If you use ReadLine at the end of the file, the special value
null
(no object) is returned. This is not an error, but if you try to apply any string methods to thenull
value returned, then you get an error!
Though print_file_lines.cs
was a nice simple illustration of a loop reading
lines, it was very verbose considering the final effect of the program,
just to print the whole file.
You can read the entire remaining contents of a file
as a single (multiline) string, using the
StreamReader
method ReadToEnd
. In place of the reading and printing
loop we could have just had:
string wholeFile = reader.ReadToEnd();
Console.Write(wholeFile);
ReadToEnd
does not strip off a newline, unlike ReadLine
,
so we do not want to add an extra newline
when writing. Here we can use the Write
method instead of WriteLine
.