---
marp: true
paginate: true
math: mathjax
theme: buutti
title: 12. Files and Streams
---

# Files and Streams

<!-- headingDivider: 5 -->
<!-- class: invert -->

## Overview

* Files and Streams
* Reading from a File
* Writing to a File
* JSON Serialization
* JSON Deserialization

## Files and streams

* The .NET library [contains classes](https://learn.microsoft.com/en-us/dotnet/api/system.io?view=net-7.0
) for manipulating files and directories
* The `FileStream` class contains methods for read and write operations for files
* The goal is usually to turn the data in your code into an outside resource, such as a .txt file, and vice versa

![](imgs/12%20Files%20and%20Streams_0.png)



## Using the `File` Class

* To get started, add the following namespace to your project:
  ```csharp
  using System.IO;
  ```
* `System.IO` provides basic directory and folder support, and allows reading and writing to files and data streams
* Check if a file exists with the `.Exists()` method:
  ```csharp
  string path = @"C:\\Users\\Public\\TestFolder\\TestFile.txt";
  if (!File.Exists(path))
  {
    Console.WriteLine($"The file {path} does not exist.");
  }
  ```

## Reading from a file

* Get the entire document as a string with `File.ReadAllText`
* Get all lines of a document as a string array with `File.ReadAllLines`
  ```csharp
  string path = @"C:\\Users\\Public\\TestFolder\\TestFile.txt";
  if (!File.Exists(path))
  {
    Console.WriteLine($"The file {path} does not exist.");
  }
  else
  {
    var allLines = File.ReadAllLines(path);
    foreach (string line in allLines)
      Console.WriteLine(line);
  }
  ```

## Writing to a file

* Writing to a file is done with `File.WriteAllText` and `File.WriteAllLines` methods:

```csharp
string path = @"C:\Users\Public\TestFolder\TestFile.txt";
string[] lyrics = new string[]
  {
    "Hello",
    "Is it me you're looking for",
    "I can see it in your eyes",
    "I can see it in your smile"
  };

if (File.Exists(path))
  File.Delete(path);                 // If file exists, deletes the old file

File.WriteAllLines(path, lyrics);    // Creates a new file with lyrics
```


## Exercise 1: A simple parser
<!--_class: exercise -->

1. Create a console application which keeps asking the user for a string. Each string is added into a list of strings.
2. If the user returns an empty string, the program asks for a path to a .txt file.
3. If the file exists, its contents are replaced with each string written to a separate line. If not, a new file with the contents is created.

## JSON

* [JSON](https://www.json.org/json-en.html) (JavaScript Object Notation) is a way to represent code objects in text form, a "data-interchange format"
* Enables reading and writing series of objects into a text file
* Consists of two types of structures
  * Objects, or unordered name-value pairs
  * Ordered lists of values
* Language-independent, although the JSON notation resembles the C-family of languages (C#, Java, JavaScript etc.)



### JSON Serialization: set-up

* .NET projects do not directly support JSON serialization
* A  *__NuGet package__*  for that exists, called  `Newtonsoft.Json`
* Install the package from  _Tools > NuGet Package Manager > Manage NuGet Packages for Solution…_
  * Select _Browse_
  * The _Newtonsoft.Json_  should be the topmost package. If not, search for it from the search bar
  * Select the package, check the checkbox next to  _Project_ and click  _Install_

### JSON Serialization: an example

You can *__serialize__* an object (e.g. a list) into a JSON string with the `JsonConvert.SerializeObject()` method:

<div class='columns21' markdown='1'>
<div markdown='1'>

  ```csharp
  class Product
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }
  static void Main(string[] args)
  {
    List<Product> products = new List<Product>
    {
      new Product{Id=1, Name="Awesome Product"},
      new Product{Id=2, Name="Terrible Product"}
    };
    string jsonString = JsonConvert.SerializeObject(products);
    File.WriteAllText(@"C:\Users\OMISTAJA\source\repos\Example\Products.json", jsonString);
  }
  ```

</div>
<div markdown='1'>

The contents of the output file Products.json are as follows:

![](imgs/12%20Files%20and%20Streams_1.png)

</div>
</div>

## JSON Deserialization

*__Deserializing__* the contents of a JSON file into an object in your program can be done with the `JsonConvert.DeserializeObject()` method:

```csharp
string json = File.ReadAllText(
  @"C:\Users\OMISTAJA\source\repos\Luentoesimerkit\Luentoesimerkit\db.json");

List<Product> deserializedProducts = new List<Product>();
deserializedProducts = JsonConvert.DeserializeObject<List<Product>>(json);

foreach(Product product in deserializedProducts)
{
  Console.WriteLine(product.Name);
}
```

## Exercise 2: Serializing Your Classes
<!--_class: exercise -->

1. Create a class `Note` with the properties `int Id`, `DateTime TimeStamp` and `string Text`.
2. Create a main loop that keeps asking the user for a new note.
3. After the user has entered each note, a new `Note` object is created with a running `Id` (1, 2, 3…), `TimeStamp` set to current time and `Text` as the inputted note. Each new note is added to a list of notes `allNotes`.
4. Every time a new note is submitted, the `allNotes` list is serialized into a json file `notes.json`. Set the path to the current project path (you can get the path by right-clicking the project in solution explorer and selecting _Copy Full Path_. Remember to change the file name!)

## Extra: FileStream
<!-- _class: extra -->

* The files have to be opened before reading or writing and closed after
* Behind the scenes, the `ReadAll...` and `WriteAll...` methods automatically do the opening and closing, as described in the Microsoft documentation:
  ![](imgs/12%20Files%20and%20Streams_2.png)
* Writing to or reading only a part of the file requires opening and closing the file manually
* This creates a *__FileStream__* object that can be used for accessing parts of the file

## FileStream (continued)
<!-- _class: "extra invert"  -->

* When inserting string data into a file, it needs to be converted into bytes

```csharp
string path = @"C:\Users\Public\TestFolder\TestWriteFile.txt";
int nOfLines = 4;
FileStream fs = null;
if (!File.Exists(path))                                       // If file does not exist
  fs = File.Create(path);                                     // create a new file and open it
else                                                          // If file exists
  fs = File.OpenWrite(path);                                  // open an existing file for writing
for (int i = 0; i < nOfLines; ++i)
{
  string input = Console.ReadLine() + "\\n";                  // Read user input, add newline
  byte[] byteInput = new UTF8Encoding(true).GetBytes(input);  // Get the string as a sequence of bytes
  fs.Write(byteInput, 0, byteInput.Length);                   // Write the bytes at the end of the file
}
fs.Close();                                                   // Close the file
```

## Extra: The `using` statement
<!-- _class: "extra invert"  -->

* From the Microsoft Documentation about the FileStream class:
  ![](imgs/12%20Files%20and%20Streams_3.png)
* This means that wrapping the file handling in an  `using` block handles the opening and closing of the stream for you

---
<!-- _class: "extra invert" -->

The following reads the first 20 characters of a file and prints them to the console

```csharp
string path = @"C:\Users\Public\TestFolder\TestFile.txt";
int numberOfBytesToPrint = 20;
byte[] byteArray = new byte[numberOfBytesToPrint];

if (!File.Exists(path))
  Console.WriteLine($"The file {path} does not exist.");
else
  using (FileStream fs = File.OpenRead(path))
  {
    fs.Read(byteArray, 0, numberOfBytesToPrint);
  };
// No need to close the file since it was handled inside of a using block

Console.WriteLine(Encoding.UTF8.GetString(byteArray, 0, byteArray.Length));
```