Правило. Единственная серьезная причина для переопределения метода Finalize() связана с использованием в классе C# неуправляемых ресурсов через P/Invoke или сложные задачи взаимодействия с СОМ (обычно посредством разнообразных членов типа System.Runtime.InteropServices.Marshal). Это объясняется тем, что в таких сценариях производится манипулирование памятью, которой исполняющая среда управлять не может.

<p id="AutBody_Root361">Переопределение метода System.Object.Finalize()</p>

В том редком случае, когда строится класс С#, в котором применяются неуправляемые ресурсы, вы вполне очевидно захотите обеспечить предсказуемое освобождение занимаемой памяти. В качестве примера создадим новый проект консольного приложения C# по имени SimpleFinalize и вставим в него класс MyResourceWrapper, в котором используется неуправляемый ресурс (каким бы он ни был). Теперь необходимо переопределить метод Finalize(). Как ни странно, для этого нельзя применять ключевое слово override языка С#:

using System;

namespace SimpleFinalize

{

  class MyResourceWrapper

  {

    // Compile-time error!

    protected override void Finalize(){ }

  }

}

На самом деле для обеспечения того же самого эффекта используется синтаксис деструктора (подобный C++). Причина такой альтернативной формы переопределения виртуального метода заключается в том, что при обработке синтаксиса финализатора компилятор автоматически добавляет внутрь неявно переопределяемого метода Finalize() много обязательных инфраструктурных элементов (как вскоре будет показано).

Финализаторы C# выглядят похожими на конструкторы тем, что именуются идентично классу, в котором определены. Вдобавок они снабжаются префиксом в виде тильды (~). Однако в отличие от конструкторов финализаторы никогда не получают модификатор доступа (они всегда неявно защищенные), не принимают параметров и не могут быть перегружены (в каждом классе допускается наличие только одного финализатора). Ниже приведен специальный финализатор для класса MyResourceWrapper, который при вызове выдает звуковой сигнал. Очевидно, такой пример предназначен только для демонстрационных целей. В реальном приложении финализатор только освобождает любые неуправляемые ресурсы и не взаимодействует с другими управляемыми объектами, даже с теми, на которые ссылается текущий объект, т.к. нельзя предполагать, что они все еще существуют на момент вызова этого метода Finalize() сборщиком мусора.

using System;

// Переопределить System.Object.Finalize()

// посредством синтаксиса финализатора.

class MyResourceWrapper

{

   // Очистить неуправляемые ресурсы.

   // Выдать звуковой сигнал при уничтожении

   // (только в целях тестирования)

  ~MyResourceWrapper() => Console.Beep();

}

Если теперь просмотреть код CIL данного финализатора с помощью утилиты ildasm.exe, то обнаружится, что компилятор добавил необходимый код для проверки ошибок. Первым делом операторы внутри области действия метода Finalize() помещены в блок try (см. главу 7). Связанный с ним блок finally гарантирует, что методы Finalize() базовых классов будут всегда выполняться независимо от любых исключений, возникших в области try.

.method family hidebysig virtual instance void

 Finalize() cil managed

 {

   .override [System.Runtime]System.Object::Finalize

   // Code size       17 (0x11)

   .maxstack  1

   .try

   {

     IL_0000:  call  void [System.Console]System.Console::Beep()

     IL_0005: nop

     IL_0006: leave.s    IL_0010

   }  // end .try

   finally

   {

     IL_0008:  ldarg.0

     IL_0009:  call instance void [System.Runtime]System.Object::Finalize()

     IL_000e:  nop

     IL_000f:  endfinally

   }  // end handler

   IL_0010:  ret

} // end of method MyResourceWrapper::Finalize

Тестирование класса MyResourceWrapper показывает, что звуковой сигнал выдается при выполнении финализатора:

using System;

using SimpleFinalize;

Console.WriteLine("***** Fun with Finalizers *****\n");

Console.WriteLine("Hit return to create the objects ");

Перейти на страницу:

Похожие книги