Created
October 28, 2025 13:02
-
-
Save sunmeat/22bcf474527837c46473ce0e6625b4df to your computer and use it in GitHub Desktop.
IDsiposable pattern C# example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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