In Xamarin Studio, go to project print_first_file.
Right click on the project in the Solution pad, and select Open Containing Folder.
Drill down two folders through bin
to Debug
.
You should find a copy of the sample.txt
that we stored there. You can open it and
look at it if you like.
Run the example program print_first_file/print_first_file.cs, shown below:
using System;
using System.IO;
namespace IntroCS
{
class PrintFirstFile // basics of reading file lines
{
public static void Main()
{
StreamReader reader = new StreamReader("sample.txt");
string line = reader.ReadLine(); // first line
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 operating system file (sample.txt
) is
associated again with a C# variable name (reader
),
this time for reading as a StreamReader
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.
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. [1]
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 while programming. This means that files provide us with our
second kind of a sequence: this time 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
in the example program print_file_lines.cs
.
This little program reads and prints the contents of a file specified by the
user, one line at a time:
using System;
using System.IO;
namespace IntroCS
{
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 with var
:
var reader = new StreamReader(userFileName);
You can use var
in place of a declared type to shorten your code
with a couple of restrictions:
We could have used this syntax long ago, but as the type names become longer, it is more useful!
Things to note about reading from files:
while
test conditions so far have been in a sense “backward looking”:
We have tested a variable that has already been set.
The test with EndOfStream
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 a while
loop testing for EndOfStream
.null
(no object)
is returned. This is not an error, but if you try to apply any string methods
to the null
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, like ReadLine
does,
so we do not want to add an extra newline
when writing. We use the Write
method instead of WriteLine
.
We have summed the numbers from 1 to n
. In that case we generated
the next number i
automatically using i++. We could also read numbers
from a file containing one number per line (plus possible white space):
static int CalcSum(string filename)
{
int sum = 0;
var reader = new StreamReader(filename);
while (!reader.EndOfStream) {
string sVal = reader.ReadLine().Trim();
sum += int.Parse(sVal);
}
reader.Close();
return sum;
}
Below and in sum_file/sum_file.cs is a more elaborate, complete example, that also exits gracefully if you give a bad file name. If you give a good file name, it skips lines that contain only whitespace.
using System;
using System.IO;
namespace IntroCS
{
class SumFile // sum a file integers, one per line
{
static void Main()
{
string filename = UI.PromptLine(
"Enter the name of a file of integers: ");
if (File.Exists(filename)) {
Console.WriteLine("The sum is {0}", CalcSum(filename));
}
else {
Console.WriteLine("Bad file name {0}", filename);
}
}
/// Open, read and close the named file and
/// return the sum of an int from
/// each line that is not just white space.
static int CalcSum(string filename)
{
int sum = 0;
var reader = new StreamReader(filename);
while (!reader.EndOfStream) {
string sVal = reader.ReadLine().Trim();
if (sVal.Length > 0) {
sum += int.Parse(sVal);
}
}
reader.Close();
return sum;
}
}
}
A useful function used in Main
for avoiding filename typo errors
is File.Exists
in the System.IO
namespace
bool File.Exists(string filenamePath)
It is true if the named files exists in the operating system’s file structure.
You should see the file sum_file/numbers.txt in the Xamarin Studio project. It is in the right form for the program. If you run the program and enter the response:
numbers.txt
you should be told that the file does not exist. Recall that the executable
created by Xamarin Studio is two directories down through bin
to Debug
. This is the default
current directory when Xamarin Studio runs the program.
You can refer to
a file that is not in the current directory.
A full description is in the next section, but briefly, what we need now:
The symbol for the parent directory is ..
.
The hierarchy of folders and files are separated by
\
in Windows and /
on a Mac, so you can test the program successfully
if you use the file name:
..\..\numbers.txt
in Windows and ../../numbers.txt
on a Mac. On a Mac, running
the program looks like:
Enter the name of a file of integers: ../../numbers.txt
The sum is 16
In FIO Helper Class we will discuss a more flexible way of finding files to open, that works well in Xamarin Studio and many other situations.
Copy sum_file.cs
to a file safe_sum_file.cs
in a new project of yours.
Modify the program: Write
a new function with the heading below. Use it in Main
, in place of the if
statement that checks (only once) for a legal file:
// Prompt the user to enter a file name to open for reading.
// Repeat until the name of an existing file is given.
// Open and return the file.
public static StreamReader PromptFile(string prompt)
A user who forgot the file name woud be stuck!
Elaborate the function and program, so that an empty line entered means
“give up”, and null
(no object) should be returned. The main program needs to
test for this and quit gracefully in that case.
Here is a simple fragment from example file copy_upper/copy_upper.cs. It copies a file line by line to a new file in upper case:
var reader = new StreamReader("text.txt");
var writer = new StreamWriter("upper_text.txt");
while (!reader.EndOfStream) {
string line = reader.ReadLine();
writer.WriteLine(line.ToUpper());
}
reader.Close();
writer.Close();
You may test this in the Xamarin Studio example project copy_upper:
bin
and then Debug
. You see text.txt
.text.txt
but not upper_text.txt
.
Leave that operating system file folder open.Debug
folder again. You should see upper_text.txt
.
You can open it and see that it holds an upper case version of the contents
of text.txt
.This is another case where the ReadToEnd
function could have eliminated the loop.
[2]
string contents = reader.ReadToEnd();
writer.Write(contents.ToUpper());
[1] | If, for some reason, you want to reread this same file while the same program is running, you need to close it and reopen it. |
[2] | Besides the speed and efficiency of this second approach,
there is also a technical improvement: There may or may not be
a newline at the end of the very last line of the file. The ReadLine
method works either way, but does not let you know the difference.
In the line-by-line version, there is always a newline after the
final line written with WriteLine .
The ReadToEnd version will have newlines exactly matching the input. |