Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created October 28, 2025 13:02
Show Gist options
  • Save sunmeat/22bcf474527837c46473ce0e6625b4df to your computer and use it in GitHub Desktop.
Save sunmeat/22bcf474527837c46473ce0e6625b4df to your computer and use it in GitHub Desktop.
IDsiposable pattern C# example
using Org.BouncyCastle.Asn1.Cmp;
using System;
using System.IO;
using System.Reflection.Metadata;
using System.Text;
namespace DisposableExample
{
// IDisposable - інтерфейс для детермінованої очистки ресурсів у .NET.
// реалізується методом Dispose(), який викликає користувач для негайного звільнення ресурсів.
// навіщо: дозволяє швидко очищати некеровані ресурси (файли, з'єднання, пам'ять) без очікування GC.
// повний pattern: Dispose() + фіналізатор + SuppressFinalize() для безпеки.
// яку проблему вирішує: витік ресурсів при довгому житті об'єкта; фіналізація повільна та негарантована.
// з using: автоматичний Dispose в finally, навіть при винятках.
// коли треба: для класів з некерованими ресурсами (stream, dbconnection); реалізується в структурах теж.
// коли ні: для чисто керованих об'єктів - gc впорається; уникайте, якщо ресурси не критичні.
// загалом, комбінуйте з фіналізатором для гарантії, але пріоритет - Dispose з using для швидкості.
// у .net 9: використовуйте IAsyncDisposable для async ресурсів, але тут базовий sync приклад.
class FileResource : IDisposable
{
private readonly string filePath;
private FileStream? stream;
private bool disposed = false;
public int Id { get; }
public FileResource(int id, string? tempFilePath = null)
{
Id = id;
filePath = tempFilePath ?? Path.GetTempFileName();
// відкриваємо файл з дескриптором (некерований ресурс)
// сам FileStream є керованим об'єктом (managed object) — це клас .NET, який існує
// в керованій купі (managed heap) і підлягає garbage collector (GC). GC автоматично
// очистить сам об'єкт FileStream, коли на нього більше не буде посилань.
// але є ключовий нюанс: FileStream тримає всередині некерований ресурс (unmanaged
// resource) — це дескриптор файлу(file handle) операційної системи (наприклад,
// Windows API handle). цей дескриптор не керується .NET GC, бо він належить ОС.
// якщо не викликати Dispose() (або не використовувати using), дескриптор може
// "зависнути" в ОС, що призводить до витоку ресурсів: файл залишиться заблокованим,
// і його не вдасться видалити чи відкрити в іншому процесі, навіть після GC.
stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
byte[] data = Encoding.UTF8.GetBytes($"Дані об'єкта {id} з {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n");
stream.Write(data, 0, data.Length);
stream.Flush();
Console.WriteLine($"Створено ресурс для об'єкта {Id}: {filePath} (заблоковано для запису/читання)");
}
// метод для роботи з ресурсом
public void ProcessFile()
{
if (disposed)
throw new ObjectDisposedException($"Ресурс {Id} вже звільнено");
if (stream == null)
throw new InvalidOperationException("Потік не ініціалізовано");
// симуляція роботи: читаємо довжину, додаємо запис
long length = stream.Length;
Console.WriteLine($"Обробка файлу {Id}: поточна довжина {length} байт");
stream.Position = length;
byte[] newData = Encoding.UTF8.GetBytes($"Додано: {DateTime.Now:HH:mm:ss}\n");
stream.Write(newData, 0, newData.Length);
stream.Flush();
Console.WriteLine($"Файл {Id} оновлено успішно");
}
// реалізація idisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // пригнічення фіналізатора, бо Dispose викликано
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// звільнення керованих ресурсів
stream?.Dispose();
stream = null;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Dispose об'єкта {Id} викликано - ресурс звільнено детерміновано");
Console.ForegroundColor = ConsoleColor.White;
}
// звільнення некерованих ресурсів (дескриптор вже в stream.dispose)
// видалення файлу після закриття
if (File.Exists(filePath))
{
try
{
File.Delete(filePath);
Console.WriteLine($"Файл {Id} видалено в Dispose");
}
catch (Exception ex)
{
Console.WriteLine($"Помилка видалення файлу {Id}: {ex.Message}");
}
}
disposed = true;
}
}
// фіналізатор - резервний, якщо Dispose не викликано
~FileResource()
{
Dispose(false);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Фіналізатор об'єкта {Id} викликано - ресурс звільнено через gc (повільно)");
Console.Beep(800, 200);
Console.ForegroundColor = ConsoleColor.White;
}
}
class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Демонстрація IDisposable pattern у .NET 9 - робота з файлами\n" +
"------------------------------------------------------------\n");
Console.ForegroundColor = ConsoleColor.White;
// приклад 1: ручний dispose з try-finally
Console.WriteLine("\nПриклад 1: Ручний Dispose з try-finally");
FileResource? resource1 = null;
try
{
resource1 = new FileResource(1);
resource1.ProcessFile();
}
finally
{
resource1?.Dispose();
}
// приклад 2: using statement (автоматичний Dispose)
Console.WriteLine("\nПриклад 2: Using statement (розгортається в try-finally)");
using (var resource2 = new FileResource(2))
{
resource2.ProcessFile();
// dispose викликається автоматично тут
}
// приклад 3: якщо забули dispose - фіналізатор спрацює при GC
Console.WriteLine("\nПриклад 3: Без Dispose - чекаємо GC для фіналізатора");
var resource3 = new FileResource(3);
resource3.ProcessFile();
Console.WriteLine("\nСпроба доступу до resource3 після 'забутого' dispose (виняток):");
try
{
resource3.ProcessFile(); // має кинути, але disposed=false
}
catch (Exception ex)
{
Console.WriteLine($"Виняток: {ex.Message}");
}
Console.WriteLine("\nНатисніть Enter для примусового GC (щоб побачити фіналізатор resource3)...");
Console.ReadLine();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("GC завершено. Фіналізатор resource3 викликано.\n");
// перевірка блокування файлу в прикладі 1 (вже dispose, має видалено)
Console.WriteLine("Перевірка: файли видалено після dispose/using.\n");
Console.WriteLine("Кількість GC за поколіннями:");
for (int gen = 0; gen <= GC.MaxGeneration; gen++)
{
Console.WriteLine($"Покоління {gen}: {GC.CollectionCount(gen)}");
}
Console.WriteLine("\nПриклад показує детерміновану (using) vs негарантовану (фіналізатором) очистку.\n" +
"Завжди використовуйте using для швидкості та безпеки.\n");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment