ZetCode

C# write text files

last modified October 18, 2026

In this article we show how to write text files in C#. Input and output operations in C# are based on streams.

A Stream is an abstract base class of all streams. A stream is an abstraction of a sequence of bytes, such as a file, an input/output device, an inter-process communication pipe, or a TCP/IP socket.

C# stream

Stream provides a generic interface to the types of input and output, and isolates the programmer from the specific details of the operating system and the underlying devices. For instance, MemoryStream works with data located in memory and FileStream with data in files.

There are several levels at which we can write text:

The file examples here run in the current directory. Relative paths are resolved against the current working directory, not the location of the program.

C# write text file with File.WriteAllText

The File.WriteAllText method creates a new file, writes the contents to the file, and then closes the file. If the target file already exists, it is overwritten.

Program.cs
var path = "data.txt";

string text = "old falcon";
File.WriteAllText(path, text);

Console.WriteLine("text written");
Console.WriteLine(File.ReadAllText(path));

// writing again replaces the content
File.WriteAllText(path, "new content");
Console.WriteLine(File.ReadAllText(path));

The example writes text to a file and reads it back.

File.WriteAllText(path, text);

The File.WriteAllText method takes the file to write to and the string to write. An optional third parameter is the encoding; if it is not specified, UTF-8 without a byte order mark is used.

$ dotnet run
text written
old falcon
new content

The second call replaced the earlier content.

C# write text with File.WriteAllLines

The File.WriteAllLines method creates a new file, writes a collection of strings to the file, one per line, and then closes the file.

Program.cs
var path = "data.txt";

string[] lines = ["old falcon", "deep forest", "golden ring"];
File.WriteAllLines(path, lines);

Console.WriteLine("lines written to file");

// any IEnumerable<string> works, so lines can be produced lazily
File.WriteAllLines("squares.txt", Enumerable.Range(1, 5).Select(i => $"{i} squared is {i * i}"));
Console.WriteLine(File.ReadAllText("squares.txt"));

The example writes three lines to a file and then a computed sequence of lines to another.

string[] lines = ["old falcon", "deep forest", "golden ring"];
File.WriteAllLines(path, lines);

This is a string array of three lines, created with a collection expression. Each element is followed by the line terminator of the platform.

File.WriteAllLines("squares.txt", Enumerable.Range(1, 5).Select(i => $"{i} squared is {i * i}"));

The method accepts any IEnumerable<string>. The lines are produced and written one by one, so a huge sequence is never held in memory.

$ dotnet run
lines written to file
1 squared is 1
2 squared is 4
3 squared is 9
4 squared is 16
5 squared is 25

C# append text to a file

The File.AppendAllText and File.AppendAllLines methods add text to the end of a file. If the file does not exist, it is created.

Program.cs
var path = "log.txt";

File.WriteAllText(path, "start" + Environment.NewLine);

File.AppendAllText(path, "second line" + Environment.NewLine);
File.AppendAllLines(path, ["third line", "fourth line"]);

Console.WriteLine(File.ReadAllText(path));

The example starts a file and appends to it.

File.WriteAllText(path, "start" + Environment.NewLine);
File.AppendAllText(path, "second line" + Environment.NewLine);

AppendAllText adds exactly the given string, so we must add the line terminator ourselves. Environment.NewLine is \n on Linux and macOS and \r\n on Windows.

$ dotnet run
start
second line
third line
fourth line

C# write text with File.WriteAllBytes

The File.WriteAllBytes method creates a new file, writes the specified byte array to the file, and then closes the file. If the target file already exists, it is overwritten.

Program.cs
using System.Text;

var path = "data.txt";

string text = "Today is a beautiful day. We go swimming and fishing.";
byte[] data = Encoding.UTF8.GetBytes(text);

File.WriteAllBytes(path, data);

Console.WriteLine("data written to file");
Console.WriteLine(new FileInfo(path).Length);

// the encoding matters for non-ASCII text
string accented = "žltý kôň";
Console.WriteLine(Encoding.ASCII.GetBytes(accented).Length);
Console.WriteLine(Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(accented)));
Console.WriteLine(Encoding.UTF8.GetBytes(accented).Length);

The example writes text into a file as bytes.

byte[] data = Encoding.UTF8.GetBytes(text);
File.WriteAllBytes(path, data);

First, we transform the text into bytes with the Encoding.UTF8.GetBytes method. The array of bytes is then written with File.WriteAllBytes. Working with bytes makes us responsible for the encoding.

Console.WriteLine(Encoding.ASCII.GetBytes(accented).Length);
Console.WriteLine(Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(accented)));
Console.WriteLine(Encoding.UTF8.GetBytes(accented).Length);

ASCII supports only 128 characters. The accented letters are replaced by question marks, and the original text is lost. UTF-8 stores them in two bytes each, so the 8 characters need 12 bytes. Use UTF-8 unless a format demands otherwise.

$ dotnet run
data written to file
53
8
?lt? k??
12

C# write text with StreamWriter

The StreamWriter writes text to a stream in a given encoding. Its WriteLine method writes a string followed by a line terminator, and Write writes without one. Visit the C# StreamWriter tutorial for details.

Program.cs
var path = "data.txt";

using var sw = new StreamWriter(path);
sw.WriteLine("old falcon");
sw.WriteLine($"{3} hawks, {2.5:F2} owls");
sw.Write("no newline here");

Console.WriteLine("data written to file");

// File.CreateText returns a StreamWriter as well
using (StreamWriter w = File.CreateText("other.txt"))
{
    w.WriteLine("created with File.CreateText");
}

The example writes lines to a text file.

using var sw = new StreamWriter(path);
sw.WriteLine("old falcon");

We create a writer and write a line. The using declaration releases the file when the sw variable goes out of scope, which also flushes the internal buffer. Without it, the data might never reach the disk.

using (StreamWriter w = File.CreateText("other.txt"))

File.CreateText is a shortcut that creates (or truncates) a file and returns a StreamWriter for it.

$ dotnet run
data written to file
$ cat data.txt
old falcon
3 hawks, 2.50 owls
no newline here

C# write text with FileStream

The Write method of FileStream writes a block of bytes to the file stream.

public override void Write(byte[] array, int offset, int count);

The first parameter is the buffer containing the data to write. The second is the zero-based offset in the array from which to begin copying bytes. The third is the number of bytes to write. There is also an overload that takes a ReadOnlySpan<byte>, which we use in the next example.

Program.cs
using System.Text;

var path = "data.txt";
File.WriteAllText(path, "0123456789 this is long old content");

// OpenWrite does not truncate the file
using (FileStream fs = File.OpenWrite(path))
{
    fs.Write(Encoding.UTF8.GetBytes("falcon\nhawk"));
}

Console.WriteLine(File.ReadAllText(path));

Console.WriteLine("--");

// File.Create (or FileMode.Create) truncates
using (FileStream fs = File.Create(path))
{
    fs.Write(Encoding.UTF8.GetBytes("falcon\nhawk"));
}

Console.WriteLine(File.ReadAllText(path));

Console.WriteLine("--");

using (var fs = new FileStream(path, FileMode.Append, FileAccess.Write))
{
    fs.Write(Encoding.UTF8.GetBytes("\nforest"));
}

Console.WriteLine(File.ReadAllText(path));

The example shows three ways of opening the file stream.

using (FileStream fs = File.OpenWrite(path))
{
    fs.Write(Encoding.UTF8.GetBytes("falcon\nhawk"));
}

File.OpenWrite opens an existing file for writing and positions the stream at the beginning, but it does not truncate it. When the new content is shorter than the old one, the tail of the old content remains in the file. This is a common source of corrupted files.

using (FileStream fs = File.Create(path))

File.Create truncates an existing file, so only the new content remains. The equivalent is FileMode.Create or FileMode.Truncate.

using (var fs = new FileStream(path, FileMode.Append, FileAccess.Write))

FileMode.Append positions the stream at the end of the file.

$ dotnet run
falcon
hawkthis is long old content
--
falcon
hawk
--
falcon
hawk
forest

C# write text asynchronously

In the previous examples, we have written text synchronously. C# also provides equivalent asynchronous methods, which free the calling thread while the operating system performs the I/O. This matters most in applications that serve many requests or have a user interface.

Program.cs
var path = "data.txt";

await File.WriteAllTextAsync(path, "an old falcon");
await File.WriteAllLinesAsync("lines.txt", ["one", "two", "three"]);
await File.AppendAllTextAsync(path, "\nand a hawk");

Console.WriteLine(await File.ReadAllTextAsync(path));

using var cts = new CancellationTokenSource();
await File.WriteAllTextAsync("c.txt", "with a token", cts.Token);

await using var sw = new StreamWriter("big.txt");

for (int i = 0; i < 1000; i++)
{
    await sw.WriteLineAsync($"line {i}");
}

Console.WriteLine("done");

In the example, we write to text files asynchronously.

await File.WriteAllTextAsync(path, "an old falcon");
await File.WriteAllLinesAsync("lines.txt", ["one", "two", "three"]);
await File.AppendAllTextAsync(path, "\nand a hawk");

The await operator suspends the method until the awaited task finishes, without blocking a thread. The asynchronous methods accept a CancellationToken as the last argument.

await using var sw = new StreamWriter("big.txt");

For incremental writing we use await using with WriteLineAsync, so even the final flush is asynchronous.

$ dotnet run
an old falcon
and a hawk
done

C# write text with an encoding

The default encoding of the .NET write methods is UTF-8 without a byte order mark (BOM). We can request another encoding or a BOM, for example for programs that need it to recognize UTF-8.

Program.cs
using System.Text;

// UTF-8 without a BOM is the default; ask for a BOM or another encoding explicitly
File.WriteAllText("plain.txt", "café");
File.WriteAllText("bom.txt", "café", new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
File.WriteAllText("utf16.txt", "café", Encoding.Unicode);
File.WriteAllText("latin1.txt", "café", Encoding.Latin1);

foreach (var f in new[] { "plain.txt", "bom.txt", "utf16.txt", "latin1.txt" })
{
    Console.WriteLine($"{f,-11} {BitConverter.ToString(File.ReadAllBytes(f))}");
}

The program writes the same word in four encodings and shows the bytes.

$ dotnet run
plain.txt   63-61-66-C3-A9
bom.txt     EF-BB-BF-63-61-66-C3-A9
utf16.txt   FF-FE-63-00-61-00-66-00-E9-00
latin1.txt  63-61-66-E9

The "é" is two bytes in UTF-8, two in UTF-16 (where every character takes two bytes), and one in Latin-1. EF BB BF and FF FE are the byte order marks of UTF-8 and little-endian UTF-16. The reading methods detect a BOM automatically.

C# write a file safely

If a program crashes, or the disk fills up, in the middle of File.WriteAllText, the target file can be left truncated. For important files, write to a temporary file first and then replace the original in one step. We also create the target directory if it does not exist, because the write methods do not.

Program.cs
// write to a temporary file first, then replace the target in one step,
// so a crash in the middle never leaves a half-written file
string path = "settings.txt";
File.WriteAllText(path, "old settings");

string tmp = path + ".tmp";
File.WriteAllText(tmp, "new settings");
File.Move(tmp, path, overwrite: true);

Console.WriteLine(File.ReadAllText(path));
Console.WriteLine(File.Exists(tmp));

// create the directory before writing into it
string dir = Path.Combine("output", "reports");
Directory.CreateDirectory(dir);          // does nothing if it already exists
File.WriteAllText(Path.Combine(dir, "report.txt"), "report");
Console.WriteLine(Path.Combine(dir, "report.txt"));

The program replaces a settings file via a temporary file and writes a file in a new directory.

File.Move(tmp, path, overwrite: true);

The File.Move overload with the overwrite parameter replaces the destination. On the same volume, the replacement is a rename, which is atomic on most file systems. For Windows-specific needs there is also File.Replace.

Directory.CreateDirectory(dir);

Directory.CreateDirectory creates all directories along the path, and does nothing if they already exist. Path.Combine joins path segments with the right separator for the platform.

$ dotnet run
new settings
False
output/reports/report.txt

Source

File class

In this article we have written to text files in various ways in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.