Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 123 124 125 126 127 128 129 130 131 ... 407
равно true, тогда

      // освободить все управляемые ресурсы.

      if (disposing)

      {

        // Освободить управляемые ресурсы.

      }

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

    }

    disposed = true;

  }

  ~MyResourceWrapper()

  {

    // Вызвать вспомогательный метод.

    // Указание false означает, что

    // очистку запустил сборщик мусора.

    CleanUp(false);

  }

}

Обратите внимание, что в MyResourceWrapper теперь определен закрытый вспомогательный метод по имени Cleanup(). Передавая ему true в качестве аргумента, мы указываем, что очистку инициировал пользователь объекта, поэтому должны быть очищены все управляемые и неуправляемые ресурсы. Однако когда очистка инициируется сборщиком мусора, при вызове методу Cleanup() передается значение false, чтобы внутренние освобождаемые объекты не освобождались (поскольку нельзя рассчитывать на то, что они все еще присутствуют в памяти). И, наконец, перед выходом из Cleanup() переменная-член disposed типа bool устанавливается в true, что дает возможность вызывать метод Dispose() много раз без возникновения ошибки.

На заметку! После того как объект был "освобожден", клиент по-прежнему может обращаться к его членам, т.к. объект пока еще находится в памяти. Следовательно, в надежном классе оболочки для ресурсов каждый член также необходимо снабдить дополнительной логикой, которая бы сообщала: "если объект освобожден, то ничего не делать, а просто возвратить управление".

Чтобы протестировать финальную версию класса MyResourceWrapper, модифицируйте свой файл Program.cs, как показано ниже:

using System;

using FinalizableDisposableClass;

Console.WriteLine("***** Dispose() / Destructor Combo Platter *****");

// Вызвать метод Dispose() вручную, что не приводит к вызову финализатора.

MyResourceWrapper rw = new MyResourceWrapper();

rw.Dispose();

// He вызывать метод Dispose(). Это запустит финализатор,

// когда объект будет обрабатываться сборщиком мусора.

MyResourceWrapper rw2 = new MyResourceWrapper();

В коде явно вызывается метод Dispose() на объекте rw, поэтому вызов деструктора подавляется. Тем не менее, мы "забыли" вызвать метод Dispose() на объекте rw2; переживать не стоит — финализатор все равно выполнится при обработке объекта сборщиком мусора.

На этом исследование особенностей управления объектами со стороны исполняющей среды через сборку мусора завершено. Хотя дополнительные (довольно экзотические) детали, касающиеся процесса сборки мусора (такие как слабые ссылки и восстановление объектов), здесь не рассматривались, полученных сведений должно быть вполне достаточно, чтобы продолжить изучение самостоятельно. В завершение главы мы взглянем на программное средство под названием ленивое (отложенное) создание объектов.

Ленивое создание объектов

При создании классов иногда приходится учитывать, что отдельная переменная-член на самом деле может никогда не понадобиться из-за того, что пользователь объекта не будет обращаться к методу (или свойству), в котором она используется. Действительно, подобное происходит нередко. Однако проблема может возникнуть, если создание такой переменной-члена сопряжено с выделением большого объема памяти.

В качестве примера предположим, что строится класс, который инкапсулирует операции цифрового музыкального проигрывателя. В дополнение к ожидаемым методам вроде Play(), Pause() и Stop() вы также хотите обеспечить возможность возвращения коллекции объектов Song (посредством класса по имени AllTracks), которая представляет все имеющиеся на устройстве цифровые музыкальные файлы.

Создайте новый проект консольного приложения по имени LazyObjectInstantiation и определите в нем следующие классы:

// Song.cs

namespace LazyObjectInstantiation

{

  // Представляет одиночную композицию.

  class Song

  {

    public string Artist { get; set; }

    public string TrackName { get; set; }

    public double TrackLength { get; set; }

  }

}

// AllTracks.cs

using System;

namespace LazyObjectInstantiation

{

  // Представляет все композиции в проигрывателе.

  class AllTracks

  {

    // Наш проигрыватель может содержать

    // максимум 10 000 композиций.

    private Song[] _allSongs = new Song[10000];

    public AllTracks()

    {

      // Предположим, что здесь производится

      // заполнение массива объектов Song.

      Console.WriteLine("Filling up the songs!");

    }

  }

}

// MediaPlayer.cs

using System;

namespace LazyObjectInstantiation

{

  // Объект MediaPlayer имеет объекты AllTracks.

  class MediaPlayer

  {

    // Предположим, что эти методы делают что-то полезное.

    public void Play()  { /* Воспроизведение композиции */ }

    public void Pause() { /* Пауза в воспроизведении */ }

    public void Stop()  { /* Останов воспроизведения */ }

    private AllTracks _allSongs = new AllTracks();

    public AllTracks GetAllTracks()

    {

      // Возвратить все композиции.

      return _allSongs;

    }

  }

}

В текущей реализации MediaPlayer предполагается, что пользователь объекта пожелает получать список объектов с помощью метода GetAllTracks(). Хорошо, а что если пользователю объекта такой список не нужен? В этой реализации память под переменную-член AllTracks по-прежнему будет выделяться, приводя тем самым к созданию 10 000 объектов Song в памяти:

using System;

using LazyObjectInstantiation;

Console.WriteLine("***** Fun with Lazy Instantiation *****n");

// В этом вызывающем коде получение всех композиций не производится,

// но косвенно все равно создаются 10 000 объектов!

MediaPlayer myPlayer = new MediaPlayer();

myPlayer.Play();

Console.ReadLine();

Безусловно, лучше не создавать 10 000 объектов, с которыми никто не будет работать, потому что в результате нагрузка на сборщик мусора .NET Core намного увеличится. В то время как можно вручную добавить код, который обеспечит создание объекта _allSongs только в случае, если он применяется (скажем, используя шаблон фабричного метода), есть более простой путь.

Библиотеки базовых классов предоставляют удобный обобщенный класс по имени Lazy<>, который определен в пространстве имен System внутри сборки mscorlib.dll. Он позволяет определять данные, которые не будут создаваться до тех пор, пока действительно не начнут применяться в коде. Поскольку класс является обобщенным, при первом его использовании вы должны явно указать тип создаваемого элемента, которым может быть любой тип из библиотек базовых классов .NET Core или специальный тип, построенный вами самостоятельно. Чтобы включить отложенную инициализацию переменной-члена AllTracks, просто приведите код

1 ... 123 124 125 126 127 128 129 130 131 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

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