C# Lists and Files
The course is part of this learning path
This course uses file reading and writing as a vehicle to illustrate important data validation concepts when mapping text data to objects and receiving user input. It demonstrates many of the essential elements present in business applications, that is: ingesting data from an external source, enabling users to modify that data, and saving the data back to permanent storage.
Robust and well-designed apps, especially concerning the user interface, need to validate input in terms of data format and applicability and provide feedback when entered data is not appropriate. We round off the course by implementing the data access layer design pattern that will enable the code to be easily adapted to other data sources.
- Read planet data from a text file into a list of planet objects within a solar system object
- Create a basic command-line user interface with data validation and feedback that writes updated planet data back to file
- Take the file reading and writing functionality and split it out into a class that returns planet objects so you can easily change data sources in the future
This course is ideal for anyone who already has some basic knowledge of C# and is looking to expand on that by learning about lists and files within the programming language.
The ideal prerequisite for this course is our C# Loops Deep Dive course, but if you know what the .NET List class is and how to create one, then you’ll be able to follow along with these lectures and demonstrations.
Demo Source Code
I have a C# program with a solar system class that reads planet data from a file and writes changed data back to the file. The file reading and writing functionality are embedded within the solar system class. I want to separate it out so that I can easily change the source of the data in the future. Let's go ahead and modify the app within Visual Studio code.
As usual, when I open the project, I'm prompted to install the .NET assets, which I will. The Data folder contains the planet data in the form of a text file, so the first thing I'll do is rename it to DataFile because I want to use the term data for my data source interface. I'll also update the file path within the code to match. Next, I'll create a new Data folder and, within that, a planets class for handling the file I/O operations. Within the datamodule namespace, I'm going to have a data namespace where all code related to data I/O will reside. As this class will be reading a file, I'll give it a private member called filename to hold the data file's path. Next, two constructors, one with no parameters and one with the file path as a parameter, that I'll assign to the private member.
I'm going to call the method to read the file GetAll because the rest of the program doesn't need to know the data is coming from a file. If at some stage down the track, I changed this code to get the planets' data from a database, I wouldn't need to change all the places where I had planets.GetAll – it would still make sense. Before I get too much further, I'll add in the required namespaces for list and file I/O functionality. Next' I'll grab the file reading code from the solar system class and paste it into the GetAll method. I don't have a planets list member in this class, so I'll need to make a local list variable. Now I need to change the name of the string array as the two names clash, and filename to _filename. There is no AddPlanet method in this class, and we don't need one, so I'll add the planets directly to the list object. The last thing to do is return the list of planets. Manually changing the name of variables, even with the standard textual replace function, is a bit tedious. In VS Code and Visual Studio, you can use the rename function by putting the cursor on the variable, hitting the F2 key, typing the new name, and then hitting enter.
We also need a method for saving the planets, which I'll call SaveAll, that will take a list of planets as its only parameter. Once again, I'll grab the code from the solar system class. After pasting it in, I'll rename the string array to keep it consistent with the GetAll method and _planets to planets to match the parameter name. Unfortunately, the F2 variable rename function doesn't work unless you're performing it on the variable declaration. In this case, I'll just have to go with the good old find and replace.
With the data access planets class all done, I now need to remove the redundant file code from the solar system class. We will have to come back to the solar system to change this constructor, but I'll make the changes to program.cs first.
Firstly, I need to create an instance of data.planets called planetData, and pass in the data file path. Next, I'll call the GetAll method to read the file and pass the planet list directly to the solar system constructor. Here's where I need to go back to solar system and modify the constructor to accept a list of planets rather than a file name. As the planet saving is happening in planetData we need to replace solarSystem.Save() with planetData.SaveAll, and pass in the solarSystem planets. We don't have a public accessor for planets, so I'll have to create one. Back to solarSystem, and create a simple public method to return the protected _planets list. Now, I can call solarSystem.Planets and pass it into planetData.SaveAll.
Before I can run this program, I'll need to change the console to integrated terminal within launch .json. Let's save a run. I'll change mars from 228 to 199 and hit enter. Great, that's all working as expected.
While we haven't made any functional changes from a user's perspective, we have separated functionality into separate code modules. In object-orientation speak, this is called separation of concerns, where code is responsible for forming just one task. This separation and modularity has benefits when targeting specific pieces of code in testing. When multiple developers are working on the same project, it is much easier for each one to work on separate pieces of code without impacting others' work when functionality is segregated like this. This demo is also a good example of the data access layer design pattern that enables simplified switching of data sources when they aren't embedded within the program's logic.
Hallam is a software architect with over 20 years experience across a wide range of industries. He began his software career as a Delphi/Interbase disciple but changed his allegiance to Microsoft with its deep and broad ecosystem. While Hallam has designed and crafted custom software utilizing web, mobile and desktop technologies, good quality reliable data is the key to a successful solution. The challenge of quickly turning data into useful information for digestion by humans and machines has led Hallam to specialize in database design and process automation. Showing customers how leverage new technology to change and improve their business processes is one of the key drivers keeping Hallam coming back to the keyboard.