На этом исследование особенностей управления объектами со стороны исполняющей среды через сборку мусора завершено. Хотя дополнительные (довольно экзотические) детали, касающиеся процесса сборки мусора (такие как слабые ссылки и восстановление объектов), здесь не рассматривались, полученных сведений должно быть вполне достаточно, чтобы продолжить изучение самостоятельно. В завершение главы мы взглянем на программное средство под названием
Ленивое создание объектов
При создании классов иногда приходится учитывать, что отдельная переменная-член на самом деле может никогда не понадобиться из-за того, что пользователь объекта не будет обращаться к методу (или свойству), в котором она используется. Действительно, подобное происходит нередко. Однако проблема может возникнуть, если создание такой переменной-члена сопряжено с выделением большого объема памяти.
В качестве примера предположим, что строится класс, который инкапсулирует операции цифрового музыкального проигрывателя. В дополнение к ожидаемым методам вроде 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 только в случае, если он применяется (скажем, используя шаблон фабричного метода), есть более простой путь.