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
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.ReadLinesgives you a file's lines one at a time in aforeach, 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 withdecimal.Parse. - A
StreamReader'sReadLine()returns the next line, ornullat the end of the file. usingcloses the file even if an exception is thrown, so it's never left open and locked.- Read a file inside a
tryandcatch, and check each line withiforTryParse, 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:
- Open the file.
- Go through it one line at a time, like rows.
- Split each line into its values, like columns, and make an object from them.
- 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:
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:
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:
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( quot;{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;
}
}
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.
isHeaderstartstrue, so the first line is skipped withcontinue, 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:
try
{
using StreamReader reader = new StreamReader("student_grades.csv");
reader.ReadLine();
string? line;
while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');
Console.WriteLine( quot;{columns[0]} has a GPA of {columns[2]}");
}
}
catch (FileNotFoundException)
{
Console.WriteLine("Can't find student_grades.csv");
}
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, ornullat the end of the file. Thewhilecondition reads a line intolineand stops atnull.usingcloses the file when thetryblock 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:
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( quot;Skipped: '{line}'");
continue;
}
Console.WriteLine( quot;{columns[0]}: {gpa}");
}
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.