.. index:: file (StreamWriter); read and close StreamReader; ReadLine close file .. _fileread: ``StreamReader`` ================== In the sample code (``first_file.cs``) of Writing Files from last section, you specified a file :file:`sample.txt`, which currently exists in the present project folder. The code is as seen below: .. code-block:: csharp :linenos: :emphasize-lines: 10-13 using System; using System.IO; namespace IntroCSCS { class Ch07File // basics of file writing { public static void Main() { StreamWriter writer = new StreamWriter("sample.txt"); writer.WriteLine("This program is writing"); writer.WriteLine("our first file."); writer.Close(); } } } Now, take a look at the following code (``print_first_file.cs``) and compare to the code above (``first_file.cs``): .. code-block:: csharp 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. .. index:: StreamReader; EndOfStream EndOfStream .. _endofstream: 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: .. code-block:: csharp 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(); } } } .. index:: var type; var ``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: - 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: :file:`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: .. index:: StreamReader; null from ReadLine ReadLine; null with StreamReader - 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 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``. - 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 the ``null`` value returned, *then* you get an error! .. index:: file (StreamWriter); ReadToEnd StreamReader; ReadToEnd ReadToEnd .. _ReadToEnd: 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``. .. .. [#readclose] 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. .. .. [#finalNewline] 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.