Basics of Working with Files and Streams in C#

Basic classes for working with files and streams in C#

File And FileInfo – these are the main classes in the namespace System.IO for working with the file system. Both classes provide methods for creating, copying, deleting, moving and opening files, but there are a couple of differences.

File — is a static class that provides methods for performing file operations. Suitable for simple file operations where there is no need to store the file state between method calls.

Example File:

// создание нового файла
string filePath = @"C:\example.txt";
File.Create(filePath).Close();

// проверка существования файла
if (File.Exists(filePath))
{
    Console.WriteLine("Файл существует.");
}

// запись текста в файл
string content = "Hello, World!";
File.WriteAllText(filePath, content);

// чтение текста из файла
string readContent = File.ReadAllText(filePath);
Console.WriteLine(readContent);

// копирование файла
string copyPath = @"C:\example_copy.txt";
File.Copy(filePath, copyPath);

// перемещение файла
string movePath = @"C:\example_moved.txt";
File.Move(copyPath, movePath);

// удаление файла
File.Delete(movePath);

FileInfo preserves the state of the file, making it better when performing multiple operations on the same file.

Example:

// создание экземпляра FileInfo
FileInfo fileInfo = new FileInfo(filePath);

// создание нового файла
using (FileStream fs = fileInfo.Create())
{
    byte[] info = new UTF8Encoding(true).GetBytes("This is some text in the file.");
    fs.Write(info, 0, info.Length);
}

// проверка существования файла
if (fileInfo.Exists)
{
    Console.WriteLine("Файл существует.");
}

// запись текста в файл
using (StreamWriter writer = fileInfo.AppendText())
{
    writer.WriteLine("Добавленный текст.");
}

// чтение текста из файла
using (StreamReader reader = fileInfo.OpenText())
{
    string s = "";
    while ((s = reader.ReadLine()) != null)
    {
        Console.WriteLine(s);
    }
}

// копирование файла
string copyFileInfoPath = @"C:\example_copy_info.txt";
fileInfo.CopyTo(copyFileInfoPath);

// перемещение файла
string moveFileInfoPath = @"C:\example_moved_info.txt";
fileInfo.MoveTo(moveFileInfoPath);

// удаление файла
fileInfo.Delete();

FileStream – used to work with files at a lower level.

Example FileStream:

string fileStreamPath = @"C:\filestream_example.txt";

// создание и запись в файл
using (FileStream fs = new FileStream(fileStreamPath, FileMode.Create))
{
    byte[] data = Encoding.UTF8.GetBytes("Hello, FileStream!");
    fs.Write(data, 0, data.Length);
}

// чтение из файла
using (FileStream fs = new FileStream(fileStreamPath, FileMode.Open, FileAccess.Read))
{
    byte[] data = new byte[fs.Length];
    int numBytesToRead = (int)fs.Length;
    int numBytesRead = 0;

    while (numBytesToRead > 0)
    {
        int n = fs.Read(data, numBytesRead, numBytesToRead);

        if (n == 0)
            break;

        numBytesRead += n;
        numBytesToRead -= n;
    }

    string text = Encoding.UTF8.GetString(data);
    Console.WriteLine(text);
}

// асинхронное чтение и запись
async Task WriteAsync(string path, string content)
{
    byte[] encodedText = Encoding.UTF8.GetBytes(content);
    using (FileStream sourceStream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None, 4096, true))
    {
        await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
    };
}

async Task<string> ReadAsync(string path)
{
    using (FileStream sourceStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
    {
        byte[] buffer = new byte[1024];
        int numRead;
        StringBuilder sb = new StringBuilder();
        while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
        {
            string text = Encoding.UTF8.GetString(buffer, 0, numRead);
            sb.Append(text);
        }
        return sb.ToString();
    }
}

// пример использования асинхронного метода
await WriteAsync(fileStreamPath, "Async Hello, FileStream!");
string asyncContent = await ReadAsync(fileStreamPath);
Console.WriteLine(asyncContent);

Directory And DirectoryInfo provide methods for creating, deleting, and listing the contents of directories. As with File And FileInfo, Directory – this is a static class, and DirectoryInfo – this is an instance class.

Example Directory:

// создание новой директории
string directoryPath = @"C:\example_dir";
Directory.CreateDirectory(directoryPath);

// проверка существования директории
if (Directory.Exists(directoryPath))
{
    Console.WriteLine("Директория существует.");
}

// перечисление файлов в директории
string[] files = Directory.GetFiles(directoryPath);
foreach (string file in files)
{
    Console.WriteLine(file);
}

// перечисление поддиректорий
string[] directories = Directory.GetDirectories(directoryPath);
foreach (string dir in directories)
{
    Console.WriteLine(dir);
}

// удаление директории
Directory.Delete(directoryPath, true);

ExampleDirectoryInfo:

// создание экземпляра DirectoryInfo
DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);

// создание новой директории
directoryInfo.Create();

// проверка существования директории
if (directoryInfo.Exists)
{
    Console.WriteLine("Директория существует.");
}

// перечисление файлов в директории
FileInfo[] fileInfos = directoryInfo.GetFiles();
foreach (FileInfo fileInfo in fileInfos)
{
    Console.WriteLine(fileInfo.Name);
}

// перечисление поддиректорий
DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories();
foreach (DirectoryInfo dirInfo in directoryInfos)
{
    Console.WriteLine(dirInfo.Name);
}

// удаление директории
directoryInfo.Delete(true);

Reading and writing files with FileStream

Creating and opening files with FileStream includes the use of different modes FileModeaccess FileAccess and sharing FileShare.

Examples of creating and opening files:

// создание нового файла
string path = @"C:\example.txt";
using (FileStream fs = new FileStream(path, FileMode.Create))
{
    byte[] info = new UTF8Encoding(true).GetBytes("This is some text in the file.");
    fs.Write(info, 0, info.Length);
}

// открытие существующего файла для чтения
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
    byte[] b = new byte[1024];
    UTF8Encoding temp = new UTF8Encoding(true);
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(temp.GetString(b));
    }
}

FileMode.Create used to create a new file that will be overwritten if it already exists. FileMode.Open opens an existing file, if it does not exist an exception will be thrown FileNotFoundException.

Reading data:

Method Read used to read bytes from a stream and write them to a specified buffer.

using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, bytesRead));
    }
}

Recording data:

Method Write used to write an array of bytes to a file.

using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
{
    byte[] info = new UTF8Encoding(true).GetBytes("More text to add to the file.");
    fs.Write(info, 0, info.Length);
}

Working with different access and sharing modes:

FileAccess defines the access level to the file: read only FileAccess.Readrecording only FileAccess.Writeor read and write FileAccess.ReadWrite.

FileShare controls access of other threads to the file: for example, FileShare.Read Allows other threads to read the file but not write to it.

// открытие файла для чтения и записи, с возможностью одновременного чтения другими потоками
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
    // чтение данных
    byte[] buffer = new byte[1024];
    int bytesRead = fs.Read(buffer, 0, buffer.Length);
    Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, bytesRead));

    // запись данных
    byte[] info = new UTF8Encoding(true).GetBytes("Additional data");
    fs.Write(info, 0, info.Length);
}

This is how you can use it FileStream With FileAccess.ReadWrite to read and write simultaneously while allowing other threads to read the file using FileShare.Read.

Working with Data Streams in C#

In .NET Stream is an abstract class that provides a generic interface for reading and writing bytes. All data streams in .NET inherit from Streamincluding FileStream, MemoryStream, NetworkStream and others.

Basic methods Stream:

  • Read(byte[] buffer, int offset, int count): reads bytes from a stream and writes them to a buffer.

  • Write(byte[] buffer, int offset, int count): writes bytes from the buffer to the stream.

  • Seek(long offset, SeekOrigin origin): moves the current position in the stream.

  • Flush(): Clears all buffers for the current stream.

Example of use Stream:

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = "example.txt";

        using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
        {
            byte[] info = new UTF8Encoding(true).GetBytes("Hello, Stream!");
            fs.Write(info, 0, info.Length);
        }

        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            byte[] b = new byte[1024];
            UTF8Encoding temp = new UTF8Encoding(true);
            while (fs.Read(b, 0, b.Length) > 0)
            {
                Console.WriteLine(temp.GetString(b));
            }
        }
    }
}

StreamReader And StreamWriter are classes for working with text data.

Example of using StreamWriter:

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = "example.txt";

        using (StreamWriter sw = new StreamWriter(path))
        {
            sw.WriteLine("Hello, StreamWriter!");
        }

        using (StreamReader sr = new StreamReader(path))
        {
            string line;
            while ((line = sr.ReadLine()) != null)
            {
                Console.WriteLine(line);
            }
        }
    }
}

BinaryReader And BinaryWriter are used to read and write data in binary format. These classes are good for working with primitive data types.

Example:

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = "example.bin";

        using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
        {
            writer.Write(1.25);
            writer.Write("Hello, BinaryWriter!");
            writer.Write(true);
        }

        using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open)))
        {
            Console.WriteLine(reader.ReadDouble());
            Console.WriteLine(reader.ReadString());
            Console.WriteLine(reader.ReadBoolean());
        }
    }
}

Asynchronous operations allow I/O tasks to be performed without blocking the main thread.

Example of asynchronous writing with StreamWriter:

using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        string path = "example.txt";

        using (StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8))
        {
            await sw.WriteLineAsync("Hello, Async StreamWriter!");
        }

        using (StreamReader sr = new StreamReader(path))
        {
            string line;
            while ((line = await sr.ReadLineAsync()) != null)
            {
                Console.WriteLine(line);
            }
        }
    }
}

Monitoring changes

FileSystemWatcher allows you to monitor the creation, modification, deletion and renaming of files and directories.

First, let's create an instance FileSystemWatcherspecifying the directory for monitoring:

using System;
using System.IO;

public class FileSystemWatcherExample
{
    private FileSystemWatcher _watcher;

    public FileSystemWatcherExample(string path)
    {
        _watcher = new FileSystemWatcher(path);
        _watcher.IncludeSubdirectories = true; // следить за подкаталогами
        _watcher.Filter = "*.*"; // следить за всеми файлами
        _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName |
                                NotifyFilters.Attributes | NotifyFilters.Size |
                                NotifyFilters.LastWrite | NotifyFilters.LastAccess |
                                NotifyFilters.CreationTime | NotifyFilters.Security;

        // Подписка на события
        _watcher.Created += OnCreated;
        _watcher.Deleted += OnDeleted;
        _watcher.Changed += OnChanged;
        _watcher.Renamed += OnRenamed;
        _watcher.Error += OnError;

        _watcher.EnableRaisingEvents = true; // включение мониторинга
    }

    private void OnCreated(object sender, FileSystemEventArgs e) => 
        Console.WriteLine($"File created: {e.FullPath}");

    private void OnDeleted(object sender, FileSystemEventArgs e) => 
        Console.WriteLine($"File deleted: {e.FullPath}");

    private void OnChanged(object sender, FileSystemEventArgs e) => 
        Console.WriteLine($"File changed: {e.FullPath}");

    private void OnRenamed(object sender, RenamedEventArgs e) => 
        Console.WriteLine($"File renamed: {e.OldFullPath} to {e.FullPath}");

    private void OnError(object sender, ErrorEventArgs e) => 
        PrintException(e.GetException());

    private void PrintException(Exception ex)
    {
        if (ex != null)
        {
            Console.WriteLine($"Message: {ex.Message}");
            Console.WriteLine("Stacktrace:");
            Console.WriteLine(ex.StackTrace);
            PrintException(ex.InnerException);
        }
    }
}

To fine-tune which changes to track, there are properties Filter And NotifyFilter:

  • Filter allows you to track changes only to certain types of files, for example only text files *.txt.

  • NotifyFilter Allows you to specify the types of changes.

Example of filter settings:

_watcher.Filter = "*.txt";
_watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size | NotifyFilters.LastWrite;

Events FileSystemWatcher include Created, Deleted, Changed, Renamed And Error. Examples of handling these events:

private void OnCreated(object sender, FileSystemEventArgs e) => 
    Console.WriteLine($"File created: {e.FullPath}");

private void OnDeleted(object sender, FileSystemEventArgs e) => 
    Console.WriteLine($"File deleted: {e.FullPath}");

private void OnChanged(object sender, FileSystemEventArgs e) => 
    Console.WriteLine($"File changed: {e.FullPath}");

private void OnRenamed(object sender, RenamedEventArgs e) => 
    Console.WriteLine($"File renamed: {e.OldFullPath} to {e.FullPath}");

private void OnError(object sender, ErrorEventArgs e) => 
    PrintException(e.GetException());

private void PrintException(Exception ex)
{
    if (ex != null)
    {
        Console.WriteLine($"Message: {ex.Message}");
        Console.WriteLine("Stacktrace:");
        Console.WriteLine(ex.StackTrace);
        PrintException(ex.InnerException);
    }
}

It would be good if you increase the size of the internal buffer using the property InternalBufferSizeif you plan to track a large number of changes. However, the maximum buffer size is limited to 64 KB.


In conclusion, I would like to remind you about the open lesson dedicated to exceptions and the nuances of working with them, which will take place on July 18.

In this lesson we will discuss what exceptions are and how we can catch and handle them. We will consider general and some specific cases of working with exceptional situations in .NET. You can sign up for the lesson on the course page “C# Developer. Professional”.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *