C# input & output
last modified October 16, 2026
In this article we cover input & output operations in C#. The input & output in C# is based on streams.
C# stream
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.
Streams transfer data from one point to another point. Streams are also capable
of manipulating the data; for example, they can compress or encrypt the data.
In .NET, the System.IO namespace contains types that enable
reading and writing on data streams and files.
C# provides higher-level methods for I/O operations in the File
class and lower-level methods in classes such as StreamReader,
StreamWriter, or FileStream.
Handling exceptions
I/O operations are error-prone. We can run into exceptions such as
FileNotFoundException or UnauthorizedAccessException.
Unlike Java, C# does not force the programmer to handle exceptions. If an
exception is not handled in a try/catch statement, the runtime
terminates the program and prints the error.
Releasing resources
I/O resources, such as open files, must be released. The resources can be
released manually in the finally clause with the
Dispose method. The using statement and the
using declaration release the resources automatically. The methods
of the File class release the resources for us.
Example text file
In the examples, we use this simple text file. It is located in the project
directory, from which we run the programs with dotnet run.
The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
C# File.ReadAllText
File provides static methods for the creation, copying, deletion,
moving, and opening of a single file, and aids in the creation of
FileStream objects.
The File.ReadAllText method opens a file, reads all text in the
file with the specified encoding, and then closes the file.
using System.Text; var path = "thermopylae.txt"; var text = File.ReadAllText(path, Encoding.UTF8); Console.WriteLine(text);
The program reads the contents of the thermopylae.txt file
and prints them to the console.
var text = File.ReadAllText(path, Encoding.UTF8);
We read the whole file into a string in one shot. In the second argument, we specify the encoding. UTF-8 is the default, so the argument can be omitted.
$ dotnet run The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
File.ReadAllText and
File.ReadAllLines load the whole file into memory. They are not
suited for reading very large files.
C# File.ReadAllLines
File.ReadAllLines opens a text file, reads all lines of the file
into a string array, and then closes the file.
var path = "thermopylae.txt";
string[] lines = File.ReadAllLines(path);
Console.WriteLine($"The file has {lines.Length} lines");
Console.WriteLine(lines[0]);
The example reads all lines from a file into a string array. We print the number of lines and the first line.
$ dotnet run The file has 3 lines The Battle of Thermopylae was fought between an alliance of Greek city-states,
C# File.ReadLines
The File.ReadLines method reads the lines lazily, one by one, as
we enumerate them. Only the current line is kept in memory; therefore, it is
suitable for large files.
var path = "thermopylae.txt";
int n = 1;
foreach (var line in File.ReadLines(path))
{
Console.WriteLine($"{n++}: {line}");
}
We print the lines of the file with line numbers.
$ dotnet run 1: The Battle of Thermopylae was fought between an alliance of Greek city-states, 2: led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the 3: course of three days, during the second Persian invasion of Greece.
C# write file
The File.WriteAllText and File.WriteAllLines methods
create a file, write the data, and close the file. If the file already exists,
it is overwritten. The File.AppendAllText and
File.AppendAllLines methods add data to the end of a file.
var path = "words.txt"; string[] words = ["sky", "cloud", "forest"]; File.WriteAllLines(path, words); File.AppendAllText(path, "ocean\n"); Console.WriteLine(File.ReadAllText(path));
We write three words to a file, append a fourth one, and print the contents of the file.
$ dotnet run sky cloud forest ocean
C# create file
The File.CreateText method creates or opens a file for writing
UTF-8 encoded text. If the file already exists, its contents are
overwritten.
var path = "cars.txt";
using (var sw = File.CreateText(path))
{
sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
sw.WriteLine("BMW");
sw.WriteLine("Volkswagen");
sw.WriteLine("Volvo");
}
Console.WriteLine(File.ReadAllText(path));
In the example, we create a cars.txt file and write some car names
into it.
using (var sw = File.CreateText(path))
The CreateText method returns a StreamWriter object.
The data is written to the file when the writer is disposed at the end of the
using block; only then can we read the complete file.
sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
...
We write five lines to the stream.
$ dotnet run Hummer Skoda BMW Volkswagen Volvo
C# file times
With the File class, we can get the creation, last write and last
access times of a file. The Exists method determines whether the
specified file exists.
var path = "cars.txt";
if (File.Exists(path))
{
Console.WriteLine(File.GetCreationTime(path));
Console.WriteLine(File.GetLastWriteTime(path));
Console.WriteLine(File.GetLastAccessTime(path));
}
If the file exists, we print its creation, last write, and last access times.
if (File.Exists(path))
The Exists method returns true if the caller has the
required permissions and the path contains the name of an existing file;
otherwise, false. This method also returns false if
the path is null, an invalid path, or a zero-length string.
$ dotnet run 10/16/2026 7:13:38 PM 10/16/2026 7:13:38 PM 10/16/2026 7:13:38 PM
C# copy file
The File.Copy method copies an existing file to a new file.
var srcPath = "cars.txt";
var destPath = "cars2.txt";
File.Copy(srcPath, destPath, true);
Console.WriteLine($"File copied: {new FileInfo(destPath).Length} bytes");
We copy the cars.txt file to cars2.txt.
File.Copy(srcPath, destPath, true);
The third parameter specifies whether the destination file should be
overwritten if it exists. Without it, an IOException is thrown.
$ dotnet run File copied: 34 bytes
C# IDisposable interface
Streams implement the IDisposable interface. Objects that implement
this interface should be disposed at the earliest opportunity. This is done by
calling the Dispose method in a finally block or by
utilizing the using statement.
StreamReader? sr = null;
var path = "thermopylae.txt";
try
{
sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());
}
catch (IOException e)
{
Console.WriteLine("Cannot read file");
Console.WriteLine(e.Message);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine("Cannot access file");
Console.WriteLine(e.Message);
}
finally
{
sr?.Dispose();
}
In this example, we read characters from a file on a disk. We manually release allocated resources.
sr = new StreamReader(path); Console.WriteLine(sr.ReadToEnd());
The StreamReader class is used to read characters. It implements
the IDisposable interface.
catch (IOException e)
{
Console.WriteLine("Cannot read file");
Console.WriteLine(e.Message);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine("Cannot access file");
Console.WriteLine(e.Message);
}
Possible exceptions are handled in the catch blocks.
finally
{
sr?.Dispose();
}
In the finally block, the Dispose method cleans up the
resources. With the null-conditional operator, we call the method only if the
variable is not null; it is null when the file could
not be opened.
$ dotnet run The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
C# using statement
The using statement defines a scope at the end of which an object
is disposed. It provides a convenient syntax that ensures the correct use of
IDisposable objects.
var path = "thermopylae.txt";
using (var sr = new StreamReader(path))
{
Console.WriteLine(sr.ReadToEnd());
}
The example reads the contents of the thermopylae.txt file. The
resources are released at the end of the using block, even if an
exception is thrown inside it.
$ dotnet run The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
C# using declaration
The using declaration is a variable declaration preceded by the
using keyword. It tells the compiler that the variable should be
disposed at the end of the enclosing scope. The using declaration is available
since C# 8.0.
var path = "thermopylae.txt"; using var sr = new StreamReader(path); Console.WriteLine(sr.ReadToEnd());
The reader is disposed when the sr variable goes out of scope; in
our program, at the end of the top-level statements.
$ dotnet run The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
C# MemoryStream
MemoryStream is a stream which works with data in computer
memory.
using var ms = new MemoryStream(6);
ms.WriteByte(9);
ms.WriteByte(11);
ms.WriteByte(6);
ms.WriteByte(8);
ms.WriteByte(3);
ms.WriteByte(7);
ms.Position = 0;
int rs = ms.ReadByte();
while (rs != -1)
{
Console.WriteLine(rs);
rs = ms.ReadByte();
}
We write six numbers to memory with a MemoryStream. Then we read
those numbers and print them to the console.
using var ms = new MemoryStream(6);
The line creates a MemoryStream object with an initial capacity of
six bytes. The capacity grows when needed.
ms.WriteByte(9); ms.WriteByte(11); ms.WriteByte(6); ...
The WriteByte method writes a byte to the stream at the current
position.
ms.Position = 0;
We set the position in the stream back to the beginning using the
Position property.
while (rs != -1)
{
Console.WriteLine(rs);
rs = ms.ReadByte();
}
Here we read all bytes from the stream and print them to the console. The
ReadByte method returns -1 at the end of the stream.
$ dotnet run 9 11 6 8 3 7
C# StreamReader
StreamReader reads characters from a byte stream. It defaults to
UTF-8 encoding.
var path = "thermopylae.txt";
using var sr = new StreamReader(path);
string? line;
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line.ToUpper());
}
We read the contents of a file line by line using the ReadLine
method and print each line in uppercase.
while ((line = sr.ReadLine()) != null)
The ReadLine method returns the next line of the stream, or
null if the end of the stream is reached.
$ dotnet run THE BATTLE OF THERMOPYLAE WAS FOUGHT BETWEEN AN ALLIANCE OF GREEK CITY-STATES, LED BY KING LEONIDAS OF SPARTA, AND THE PERSIAN EMPIRE OF XERXES I OVER THE COURSE OF THREE DAYS, DURING THE SECOND PERSIAN INVASION OF GREECE.
C# count lines
In the next example, we count the lines of a file.
int count = 0;
var path = "thermopylae.txt";
using var sr = new StreamReader(path);
while (sr.ReadLine() != null)
{
count++;
}
Console.WriteLine($"There are {count} lines");
We use a StreamReader and a while loop. The same result can be
achieved with File.ReadLines(path).Count().
$ dotnet run There are 3 lines
C# StreamWriter
StreamWriter writes characters to a stream in a particular
encoding.
var path = "newfile.txt";
using (var sw = new StreamWriter(path))
{
sw.WriteLine("Today is a beautiful day.");
}
using (var sw = new StreamWriter(path, append: true))
{
sw.WriteLine("We go swimming.");
}
Console.Write(File.ReadAllText(path));
The example writes a line to a new file with StreamWriter and then
appends another line.
using (var sw = new StreamWriter(path))
We create a new StreamWriter. The default encoding is UTF-8. If
the file exists, it is overwritten; otherwise, a new file is created.
using (var sw = new StreamWriter(path, append: true))
With the append parameter set to true, the data is added to the
end of an existing file.
$ dotnet run Today is a beautiful day. We go swimming.
C# FileStream
FileStream provides a stream for a file, supporting both
synchronous and asynchronous read and write operations.
StreamReader and StreamWriter work with text data,
while FileStream works with bytes.
using System.Text;
var path = "newfile2.txt";
using (var fs = new FileStream(path, FileMode.Create))
{
var text = "Фёдор Михайлович Достоевский\n";
byte[] bytes = Encoding.UTF8.GetBytes(text);
fs.Write(bytes, 0, bytes.Length);
Console.WriteLine($"{text.Length} characters, {bytes.Length} bytes");
}
Console.Write(File.ReadAllText(path));
We write some text in Russian Cyrillic to a file.
using (var fs = new FileStream(path, FileMode.Create))
A FileStream object is created. The second parameter is the mode
in which the file is opened. The Create mode creates a new file or
overwrites an existing one. With FileMode.Append, we would add the
data to the end of the file.
byte[] bytes = Encoding.UTF8.GetBytes(text);
The text is encoded into an array of bytes. In UTF-8, each Cyrillic letter takes two bytes.
fs.Write(bytes, 0, bytes.Length);
We write the bytes to the file stream.
$ dotnet run 29 characters, 55 bytes Фёдор Михайлович Достоевский
C# asynchronous file I/O
The File class has asynchronous versions of its methods. They do
not block the calling thread while waiting for the disk, which matters in GUI
and server applications.
var path = "thermopylae.txt";
string text = await File.ReadAllTextAsync(path);
Console.WriteLine($"{text.Length} characters");
await foreach (var line in File.ReadLinesAsync(path))
{
Console.WriteLine(line.Split(' ')[0]);
}
await File.WriteAllTextAsync("copy.txt", text);
Console.WriteLine(File.Exists("copy.txt"));
We read the file asynchronously as a whole and line by line, and write a copy of it.
await foreach (var line in File.ReadLinesAsync(path))
The File.ReadLinesAsync method, available since .NET 7, returns
an IAsyncEnumerable<string>, which we iterate with
await foreach. We print the first word of each line.
$ dotnet run 223 characters The led course True
C# console input
The console is a stream too. The Console.ReadLine method reads a
line from the standard input. It returns null when the input ends,
for example when the input is redirected from a file or a pipe.
int lines = 0;
int words = 0;
string? line;
while ((line = Console.ReadLine()) != null)
{
lines++;
words += line.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
Console.WriteLine($"lines: {lines}, words: {words}");
The program counts the lines and words of its standard input.
$ cat thermopylae.txt | dotnet run lines: 3, words: 38
We pipe the contents of the file to the program. When we run the program interactively, we type the text and end the input with Ctrl+D on Unix or Ctrl+Z and Enter on Windows.
C# XmlReader
We can use streams to read XML data. The XmlReader class provides
fast, non-cached, forward-only access to XML data.
We have the following XML test file:
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language>Python</language>
<language>Ruby</language>
<language>JavaScript</language>
<language>C#</language>
</languages>
This file contains language names between custom XML tags.
using System.Xml;
var path = "languages.xml";
using var reader = XmlReader.Create(path);
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "language")
{
Console.WriteLine($"{reader.Name}: {reader.ReadElementContentAsString()}");
}
}
This example reads data from the XML file and prints it to the terminal.
using var reader = XmlReader.Create(path);
An XmlReader object is created with the Create
factory method. The older XmlTextReader class still works, but
XmlReader.Create is the recommended way.
while (reader.Read())
The Read method moves to the next node of the document. It returns
false if there are no more nodes left.
Console.WriteLine($"{reader.Name}: {reader.ReadElementContentAsString()}");
For each language element, we print its name and its text
content.
$ dotnet run language: Python language: Ruby language: JavaScript language: C#
C# create and move directory
The Directory class has static methods for creating, moving, and
enumerating through directories and subdirectories.
Directory.CreateDirectory("temp");
Directory.CreateDirectory("newdir");
if (!Directory.Exists("temporary"))
{
Directory.Move("temp", "temporary");
}
Console.WriteLine(Directory.Exists("temp"));
Console.WriteLine(Directory.Exists("temporary"));
Console.WriteLine(Directory.Exists("newdir"));
We create two directories and rename one of them. The directories are created in the current working directory.
Directory.CreateDirectory("temp");
The CreateDirectory method creates a new directory. It does
nothing if the directory already exists.
Directory.Move("temp", "temporary");
The Move method gives a directory a new name. It throws an
IOException if the destination already exists; therefore, we check
it first.
$ dotnet run False True True
C# DirectoryInfo
DirectoryInfo exposes instance methods for creating, moving,
and enumerating through directories and subdirectories.
var dirInfo = new DirectoryInfo(".");
Console.WriteLine("Directories:");
foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name))
{
Console.WriteLine($" {dir.Name}");
}
Console.WriteLine("Text files:");
foreach (var file in dirInfo.EnumerateFiles("*.txt").OrderBy(f => f.Name))
{
Console.WriteLine($" {file.Name,-16} {file.Length,5} B");
}
We use the DirectoryInfo class to list the subdirectories and the
text files of the current directory.
foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name))
The EnumerateDirectories method returns the subdirectories as
DirectoryInfo objects. The order is not guaranteed, so we sort them
by name.
foreach (var file in dirInfo.EnumerateFiles("*.txt").OrderBy(f => f.Name))
The EnumerateFiles method takes a search pattern. It returns
FileInfo objects, which give us the name and the size of each
file.
$ dotnet run Directories: bin newdir obj temporary Text files: cars.txt 34 B cars2.txt 34 B copy.txt 223 B newfile.txt 42 B newfile2.txt 55 B thermopylae.txt 223 B words.txt 23 B
The directories and files were created by the previous examples; the
bin and obj directories belong to the project.
Source
In this article we have covered input/output operations in C#.
Author
List all C# tutorials.