# Reading a File in C#

Read a text file like a tiny database. Open it, go through it line by line with File.ReadLines or a StreamReader, split each CSV line into an object, and close it with using, all inside a try and catch.

Apprentice · C# · Updated November 3, 2026

## After this lesson you can

- Read a text file line by line with File.ReadLines or a StreamReader, inside a try and catch
- Turn each line of a CSV file into an object, skipping the header line

## Key takeaways

- `File.ReadLines` gives you a file's lines one at a time in a `foreach`, and closes the file when the loop ends.
- Skip a CSV file's header line, then cut each line into columns with `line.Split(',')` and convert each one, such as the GPA with `decimal.Parse`.
- A `StreamReader`'s `ReadLine()` returns the next line, or `null` at the end of the file.
- `using` closes the file even if an exception is thrown, so it's never left open and locked.
- Read a file inside a `try` and `catch`, and check each line with `if` or `TryParse`, skipping the bad ones instead of letting one stop the whole file.

Think of a text file as a very simple database. Reading one follows the same steps as reading rows with a DAO:

1. Open the file.
2. Go through it **one line at a time**, like rows.
3. Split each line into its values, like columns, and make an object from them.
4. Close the file.

## A CSV file

A **CSV** file (*comma-separated values*) holds a table as plain text: one row per line, with commas between the values. Spreadsheets save and open them. Here's `student_grades.csv`; its first line is a **header** naming the columns:

```text
firstname,lastname,gpa
Emily,Wilson,3.91
Sarah,Johnson,3.31
John,Smith,2.55
Anna,Williams,3.02
Jane,Garcia,3.96
```

Each line will become one `Student`:

```csharp
public class Student
{
    public string FirstName { get; }
    public string LastName { get; }
    public decimal Gpa { get; }

    public Student(string firstName, string lastName, decimal gpa)
    {
        FirstName = firstName;
        LastName = lastName;
        Gpa = gpa;
    }
}
```

## The quick way: File.ReadLines

**`File.ReadLines`** opens the file, gives you its lines one at a time in a `foreach`, and closes the file when the loop ends:

```csharp
List<Student> students = new List<Student>();

try
{
    bool isHeader = true;
    foreach (string line in File.ReadLines("student_grades.csv"))
    {
        if (isHeader)
        {
            isHeader = false;
            continue;
        }

        string[] columns = line.Split(',');
        Student student = new Student(columns[0], columns[1], decimal.Parse(columns[2]));
        students.Add(student);
    }
}
catch (FileNotFoundException)
{
    Console.WriteLine("Can't find student_grades.csv");
}

foreach (Student student in students)
{
    Console.WriteLine($"{student.LastName}, {student.FirstName}: {student.Gpa}");
}

public class Student
{
    public string FirstName { get; }
    public string LastName { get; }
    public decimal Gpa { get; }

    public Student(string firstName, string lastName, decimal gpa)
    {
        FirstName = firstName;
        LastName = lastName;
        Gpa = gpa;
    }
}
```

```text
Wilson, Emily: 3.91
Johnson, Sarah: 3.31
Smith, John: 2.55
Williams, Anna: 3.02
Garcia, Jane: 3.96
```

Step by step:

- **The header is skipped.** `isHeader` starts `true`, so the first line is skipped with `continue`, and every line after it is read.
- **`line.Split(',')`** cuts the line at each comma: `"Emily,Wilson,3.91"` becomes `{ "Emily", "Wilson", "3.91" }`.
- **Each column is converted** to the property's type: the GPA with `decimal.Parse`.
- **It's all in a `try`.** If the file isn't there, the program says so instead of crashing.

There's also `File.ReadAllLines`, which reads every line into a `string[]` at once. It's handy for small files, but `File.ReadLines` reads one line at a time, so even a file of a million lines never has to fit in memory all at once.

## The longhand way: StreamReader

Under the hood, reading a file means opening a **stream**: a connection to the file that hands over its contents a piece at a time. A **`StreamReader`** reads a stream as text. You'll meet it in older code and whenever you need more control:

```csharp
try
{
    using StreamReader reader = new StreamReader("student_grades.csv");

    reader.ReadLine();

    string? line;
    while ((line = reader.ReadLine()) != null)
    {
        string[] columns = line.Split(',');
        Console.WriteLine($"{columns[0]} has a GPA of {columns[2]}");
    }
}
catch (FileNotFoundException)
{
    Console.WriteLine("Can't find student_grades.csv");
}
```

```text
Emily has a GPA of 3.91
Sarah has a GPA of 3.31
John has a GPA of 2.55
Anna has a GPA of 3.02
Jane has a GPA of 3.96
```

- The first `reader.ReadLine()` reads the header and throws it away.
- `ReadLine()` returns the next line, or `null` at the end of the file. The `while` condition reads a line into `line` and stops at `null`.
- **`using`** closes the file when the `try` block ends, the same way it closed your database connections. It closes it **even if an exception is thrown** halfway through, so the file is never left open and locked.

## Bad lines

Real files have mistakes in them: a blank line at the end, a missing column, a GPA of `n/a`. One bad line shouldn't stop the whole file. Check each line, and skip the ones you can't use:

```csharp
string[] lines = { "Emily,Wilson,3.91", "", "John,Smith", "Anna,Williams,n/a", "Jane,Garcia,3.96" };

foreach (string line in lines)
{
    string[] columns = line.Split(',');
    if (columns.Length != 3 || !decimal.TryParse(columns[2], out decimal gpa))
    {
        Console.WriteLine($"Skipped: '{line}'");
        continue;
    }
    Console.WriteLine($"{columns[0]}: {gpa}");
}
```

```text
Emily: 3.91
Skipped: ''
Skipped: 'John,Smith'
Skipped: 'Anna,Williams,n/a'
Jane: 3.96
```

That's *prevent what you can check*, from the last lesson: a bad line is something you expect, so it gets an `if`, not a `catch`.

## Check yourself

2 questions to try on the lesson page.

## So what

Open, loop, split, close: reading a file is the same pattern as reading a database table. Put it in a `try`, let `using` close the file, and check each line before you trust it.

## Key terms

- CSV File
- [Stream](https://www.ask-codey.com/glossary/stream-c-sharp)
- [StreamReader](https://www.ask-codey.com/glossary/streamreader)
- [using Statement](https://www.ask-codey.com/glossary/using-statement)

## Cheat sheets

- [Reading a File in C#](https://www.ask-codey.com/cheat-sheets/reading-a-file-in-csharp-cheat-sheet)

Source: https://www.ask-codey.com/lessons/reading-a-file-in-csharp
