Created
October 28, 2025 12:31
-
-
Save sunmeat/da01c67559879ddd9cfc26270a25d719 to your computer and use it in GitHub Desktop.
finalizer example C#
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 System.Text; | |
| using System.IO; | |
| namespace FinalizeExample | |
| { | |
| // фіналізація - механізм .net для автоматичного очищення некерованих ресурсів об'єкта під час GC. | |
| // це перевизначення методу finalize() класу object, яке реалізується через деструктор ~class(). | |
| // навіщо: гарантує, що ресурси (файли, дескриптори, пам'ять) звільняються навіть якщо розробник забув викликати Dispose(). | |
| // яку проблему вирішує: витік ресурсів при несподіваному завершенні програми або помилках, коли Dispose не викликано. | |
| // коли треба: при роботі з некерованими ресурсами (native api, файли через pinvoke, dll). | |
| // коли ні: для чисто керованих об'єктів - gc сам очистить; фіналізація уповільнює gc (додатковий цикл, окремий потік). | |
| // порада: уникайте використання фіналізаторів - використовуйте IDisposable з using для детермінованої очистки. | |
| class FileHolder | |
| { | |
| public string FilePath { get; } | |
| private FileStream? stream; | |
| public int Id { get; } | |
| public FileHolder(int id, string? tempFilePath = null) | |
| { | |
| Id = id; | |
| FilePath = tempFilePath ?? Path.GetTempFileName(); | |
| // справжнє відкриття файлу з утриманням дескриптора (неуправляемий ресурс) | |
| stream = new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None); | |
| byte[] data = Encoding.UTF8.GetBytes($"Дані об'єкта {id}\n"); | |
| stream.Write(data, 0, data.Length); | |
| stream.Flush(); | |
| Console.WriteLine($"Створено та відкрито файл для об'єкта {Id}: {FilePath} (заблоковано дескриптором)"); | |
| } | |
| // фіналізатор - викликається gc асинхронно в окремому потоці, не детерміновано. | |
| ~FileHolder() | |
| { | |
| Cleanup(); | |
| Console.ForegroundColor = ConsoleColor.Yellow; | |
| Console.WriteLine($"Фіналізатор об'єкта {Id} викликано - дескриптор файлу {FilePath} закрито автоматично"); | |
| Console.Beep(800, 200); // звук для демонстрації виклику | |
| } | |
| private void Cleanup() | |
| { | |
| stream?.Dispose(); | |
| stream = null; | |
| if (File.Exists(FilePath)) | |
| { | |
| try | |
| { | |
| File.Delete(FilePath); | |
| Console.WriteLine($"Файл об'єкта {Id} видалено в фіналізаторі"); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Помилка видалення файлу {Id}: {ex.Message}"); | |
| } | |
| } | |
| } | |
| } | |
| class Program | |
| { | |
| static void Main() | |
| { | |
| Console.OutputEncoding = Encoding.UTF8; | |
| Console.ForegroundColor = ConsoleColor.Green; | |
| Console.WriteLine("Демонстрація фіналізації в .NET - робота зі справжніми файлами\n" + | |
| "--------------------------------------------------------------\n"); | |
| Console.ForegroundColor = ConsoleColor.White; | |
| // створюємо 3 об'єкти з фіналізаторами - вони відкривають файли з дескрипторами | |
| var holders = new FileHolder[3]; | |
| for (int i = 0; i < 3; i++) | |
| { | |
| holders[i] = new FileHolder(i + 1); | |
| Console.WriteLine($"Об'єкт {i + 1} у поколінні {GC.GetGeneration(holders[i])}"); | |
| } | |
| Console.WriteLine("\nФайли створено та заблоковано. Спроба видалення файлу об'єкта 1 (має провалитися):"); | |
| try | |
| { | |
| File.Delete(holders[0].FilePath); | |
| Console.WriteLine("Видалено! (не повинно статися)"); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Не вдалося видалити: {ex.Message} (файл заблоковано дескриптором)"); | |
| } | |
| Console.WriteLine("\nНатисніть Enter для першого GC..."); | |
| Console.ReadLine(); | |
| // перший gc: об'єкти маркуються для фіналізації, але деструктори не викликаються | |
| GC.Collect(0, GCCollectionMode.Forced, true); | |
| Console.WriteLine("Перший GC завершено. Файли ще заблоковані, фіналізатори очікують.\n"); | |
| Console.WriteLine("Перевірте тимчасову папку - файли існують і заблоковані.\n"); | |
| Console.WriteLine("Натисніть Enter для другого GC (виклик фіналізаторів у фоновому потоці)..."); | |
| Console.ReadLine(); | |
| // другий gc: фіналізатори виконуються, дескриптори закриваються, файли видаляються | |
| GC.Collect(); | |
| GC.WaitForPendingFinalizers(); // чекаємо завершення фіналізаторів | |
| GC.Collect(); // збір для повного очищення | |
| Console.WriteLine("Другий GC завершено. Фіналізатори викликані.\n"); | |
| // перевірка після фіналізації | |
| Console.WriteLine("Спроба видалення файлу об'єкта 1 після GC (має не знайти файл):"); | |
| try | |
| { | |
| File.Delete(holders[0].FilePath); | |
| Console.WriteLine("Видалено! (не повинно статися)"); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Результат: {ex.Message} (файл уже видалено в фіналізаторі)"); | |
| } | |
| Console.WriteLine("Перевірте тимчасову папку - файли видалено.\n"); | |
| Console.WriteLine("Кількість GC за поколіннями:"); | |
| for (int gen = 0; gen <= GC.MaxGeneration; gen++) | |
| { | |
| Console.WriteLine($"Покоління {gen}: {GC.CollectionCount(gen)}"); | |
| } | |
| Console.WriteLine("\nПриклад показує, як фіналізація гарантує очищення дескрипторів і видалення файлів,\n" + | |
| "але уповільнює процес. Краще використовувати IDisposable з using для детермінованої очистки.\n"); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment