How to Save a list of strings to a file in C#

By FoxLearn 2/5/2025 9:30:18 AM   4
The simplest way to save a list of integers to a file is by using File.WriteAllLines().

For example:

var scores = new List<int>()
{
    92,
    85,
    78
};

System.IO.File.WriteAllLines(@"C:\temp\scores.txt", scores.Select(s => s.ToString()));

This creates (or overwrites) the file and writes each integer as a string on a new line. The resulting file looks like this:

92\r\n
85\r\n
78\r\n

The newline characters \r\n are shown here for clarity.

Specifying the separator character

What if you want to separate each integer with a semicolon (or another separator character), rather than placing each integer on a new line?

To do that, you can join the integers with a separator character and then use File.WriteAllText().

var scores = new List<int>()
{
    92,
    85,
    78
};

var semicolonSeparatedScores = string.Join(';', scores);

System.IO.File.WriteAllText(@"C:\temp\scores.txt", semicolonSeparatedScores);

This creates (or overwrites) the specified file, outputting the integers separated by a semicolon:

92;85;78

Reading the integers from a file into a list

When each integer is on a new line

To read the integers from a file into a list, you can either read the file line by line with File.ReadLines() or read the entire file at once with File.ReadAllLines().

// As an array
int[] scoresArray = System.IO.File.ReadAllLines(@"C:\temp\scores.txt").Select(int.Parse).ToArray();

// As a list
using System.Linq;
List<int> scores = System.IO.File.ReadAllLines(@"C:\temp\scores.txt").Select(int.Parse).ToList();

// As an enumerable if you don't need to keep the integers around
IEnumerable<int> scores = System.IO.File.ReadLines(@"C:\temp\scores.txt").Select(int.Parse);

When the integers are separated with a different character

To get the integers back into a list when they are separated by a different character, you'll need to read the file and split the string by that separator.

// As an array
int[] scores = System.IO.File.ReadAllText(@"C:\temp\scores.txt").Split(';').Select(int.Parse).ToArray();

// As a list
using System.Linq;
var scores = System.IO.File.ReadAllText(@"C:\temp\scores.txt").Split(';').Select(int.Parse).ToList();

This reads the entire file, which might not be ideal for large files. If you don't want to load the whole file into memory at once, see the memory-efficient approach below.

Get an IEnumerable<int> when using a different separator character

If you want to avoid loading the entire file into memory and are dealing with non-newline separators, you can use the following memory-efficient generator method. This reads blocks of characters from the file stream and yields strings once a separator is encountered.

using System.IO;

public static IEnumerable<int> ReadIntegers(string path, char separator)
{
    var sb = new StringBuilder();
    using (var sr = new StreamReader(path))
    {
        char[] buffer = new char[1024];
        int charsRead = 0;
        int charBlockIndex = 0;
        int charBlockCount = 0;

        while (!sr.EndOfStream)
        {
            charBlockIndex = 0;
            charBlockCount = 0;
            charsRead = sr.Read(buffer, 0, buffer.Length);
            for (int i = 0; i < charsRead; i++)
            {
                if (buffer[i] == separator)
                {
                    sb.Append(buffer, charBlockIndex, charBlockCount);
                    yield return int.Parse(sb.ToString());
                    sb.Clear();
                    charBlockIndex = i + 1;
                    charBlockCount = 0;
                }
                else
                {
                    charBlockCount++;
                }
            }

            if (charBlockCount > 0)
                sb.Append(buffer, charBlockIndex, charBlockCount);
        }

        if (sb.Length > 0)
            yield return int.Parse(sb.ToString());
    }
}

This approach reads the file in chunks and yields the integers one by one, which can significantly reduce memory usage.

Performance Comparison

Here’s a performance comparison between this generator method and the ReadAllText().Split() approach:

MethodNumIntegersMeanAllocated
ReadAllText_Split10,0003.5 ms2,600 KB
Generator10,0002.7 ms1,000 KB
ReadAllText_Split100,00045.5 ms26,000 KB
Generator100,00025.3 ms9,400 KB
ReadAllText_Split1,000,000420 ms250,000 KB
Generator1,000,000240 ms95,000 KB

As seen in the table, the generator method is faster and uses much less memory, particularly with larger files. In the 1 million integer test, the generator used only 8 MB of memory compared to the 200 MB used by ReadAllText().Split().