Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Правильная реализация метода Dispose

#c_sharp #классы #сборщик_мусора


Есть класс А, у него 4 текстовых поля А1,А2,А3,А4.
Я хочу реализовать метод  Dispose для класса, т.к. в его полях находится большой
текст, а используется он всего 1 раз.

Скажите, будет ли достаточно в моем случае, если сделаю так?

Dispose()
{
   А1 = null;
   А2 = null;
   А3 = null;
   А4 = null;
}


Или может нужно просто сделать А1 = ""?
    


Ответы

Ответ 1



Если вы не работаете в своем классе с неуправляемыми ресурсами и вы не планируете использовать свой класс вместе с using, то реализовывать IDisposable не нужно. И обнулять ссылки тоже не нужно. Метод Dispose определен в интерфейсе IDisposable, который используется в .NET Framework в разных классах. На реализацию IDisposable можно посмотреть в исходниках. (Надесь никто не будет спорить, что в исходниках .NET правильные реализации этого интерфейса). Там же можно посмотреть на комментарии разработчиков .NET На скриншоте страницы справа - комментарии разработчиков, а слева - это ссылки на реализацию IDisposable в разных классах. Один из примеров реализации IDisposable из .NET Framework: class SimpleMonitor : IDisposable { public void Enter() { ++ _busyCount; } public void Dispose() { -- _busyCount; } public bool Busy { get { return _busyCount > 0; } } int _busyCount; } Взят тут. Ничего особенного, это если не надо работать с неуправляемыми ресурсами. ВАЖНО: при работе с неуправляемыми ресурсам надо использовать SafeHandle. Пример тут, а краткое описание под скриншотом. Почитайте описание класса в MSDN, посмотрите на реализацию в исходниках, также посмотрите базовые классы. Очевидно, что там все достаточно сложно. Поэтому не придумывайте велосипеды, несмотря на то, что некоторые это предлагают с серьезным видом :) Как использования SafeHandle? Например надо с помощью WinAPI функции FindFirstFileEx получить информацию о папке. Функция возвращает HANDLE -- это неуправляемый ресурс, который обязательно надо освободить с помощью WinAPI функции FindClose. В такой ситуации надо определить класс SafeHandle using Microsoft.Win32.SafeHandles; class SafeHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeHandle() : base(true) { } protected override bool ReleaseHandle() { return FindClose(this.handle); } } И указать SafeHandle в определении функции static extern SafeHandle FindFirstFileEx(...) В своем классе пишем var h = FindFirstFileEx(...); Таким образом мы получаем ссылку на специальную обертку над неуправляемым ресурсом. И даже если произойдет неожиданное прерывание потока или переполнение стека и т.д., то будет вызвана функция FindClose, т.е. гарантированно будет закрыт дескриптор неуправляемого ресурса. Работающий пример тут.

Ответ 2



Отвечая на ваш вопрос об обнулении ссылок -- нет, обнулять их не нужно, поскольку это не поможет. Более того, в вашем случае метод Dispose вам совсем не нужен. Почему, читайте ниже. Существует только две ситуации, когда необходимо реализовывать IDisposable: В классе есть управляемые (IDisposable) ресурсы В классе есть неуправляемые ресурсы В классе есть управляемые (IDisposable) ресурсы У всех таких ресурсов нужно вызвать метод Dispose() или его аналог (например, Close()). Зануллять такие ресурсы в 99.9% случаев нет необходимости, поскольку они в любом случае будут собраны сборщиком мусора. (Зануллять можно, если эти ресурсы занимают большой объем памяти и вы хотите "посигнализировать" сборщику мусора чтобы он побыстрее их собрал. Но никаких гарантий это не дает.) public sealed class SingleApplicationInstance : IDisposable { private Mutex namedMutex; private bool namedMutexCreatedNew; public SingleApplicationInstance(string applicationName) { this.namedMutex = new Mutex(false, applicationName, out namedMutexCreatedNew); } public bool AlreadyExisted { get { return !this.namedMutexCreatedNew; } } public void Dispose() { namedMutex.Close(); } } В классе есть неуправляемые ресурсы Реализация Dispose() для таких классов должна "закрыть" ресурс (как -- зависит от самого ресурса) и вызвать GC.SuppressFinalize(this);. Плюс обязательно должен быть реализован финализатор, в котором должно вызываться "закрытие" ресурса. Т.о. гарантируется, что ресурс будет закрыт в любом случае -- либо программистом (при этом финализатор вызва не будет), либо сборщиком мусора (путем вызова финализатора). public sealed class WindowStationHandle : IDisposable { public WindowStationHandle(IntPtr handle) { this.Handle = handle; } public WindowStationHandle() : this(IntPtr.Zero) { } public bool IsInvalid { get { return (this.Handle == IntPtr.Zero); } } public IntPtr Handle { get; set; } private void CloseHandle() { if (this.IsInvalid) { return; } if (!NativeMethods.CloseWindowStation(this.Handle)) { Trace.WriteLine("CloseWindowStation: " + new Win32Exception().Message); } this.Handle = IntPtr.Zero; } public void Dispose() { this.CloseHandle(); GC.SuppressFinalize(this); } ~WindowStationHandle() { this.CloseHandle(); } } internal static partial class NativeMethods { [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseWindowStation(IntPtr hWinSta); } Полная версия о правильном применении IDisposable -- в моем переводе на Хабре.

Ответ 3



Паттерн IDisposable в вашем случае не применим, т.к. он служит для освобождения внешних ресурсов, а не памяти. Ресурс в это случае - это нечто, что требует явного закрытия - файл, сокет, транзакция. Память же в .NET ручного освобождения не требует. Кроме освобождения ресурсов IDisposable часто реализуют только ради использования синтаксиса using, но это тоже явно не ваш случай. Освобождением памяти в .NET занимается сборщик мусора. Когда он решает, что память стоило бы немного освободить, он отслеживает достижимость объектов от т.н. корней - локальных переменных, глобальных статический полей и прочих способов хоть как-то добраться до объекта из кода. Предположим, у вас есть локальная переменная, ссылающаяся на объект вашего класса. Сборщик мусора проверяет достижимость: локальная переменная → ваш объект → большая строка видит, что строка используется, и не освобождает из нее память. Но вот вы вышли из метода. Или код в методе прошел дальше последнего упоминания вашей переменной в коде. На ваш объект больше нет ссылок ниоткуда: ваш объект → большая строка И объект, и большая строка недостижимы. И сборщик мусора их спокойно сжигает при следующей сборке. Изменило бы ситуацию зануление ссылок (в Dispose, или в другом методе)? Совсем нет. Зануление в этой ситуации - совершенно излишне.

Ответ 4



Да, можно. null. Но вообще, есть вероятность, что это просто лишнее. Такое имеет смысл делать только если эти строки реально влияют на память, а объект, содержащий их, живёт значительно дольше. В любом случае, странно использовать именно dispose для этого. Кстати, рекомендуется проверять, был ли вызван dispose в других методах. В твоём случае, вероятно, в методе, использующем эти строки.

Комментариев нет:

Отправить комментарий