, важно обратить внимание на изменение также и реализации показанного выше метода
GetAllTracks
. В частности, для получения актуальных сохраненных данных (в этом случае объекта
AllTracks
, поддерживающего 10 000 объектов
Song
) должно применяться доступное только для чтения свойство
Value
класса
Lazy<>
.
Взгляните, как благодаря такому простому изменению приведенный далее модифицированный код будет косвенно
размещать объекты
Song
в памяти, только если метод
GetAllTracks
действительно вызывается:
Console.WriteLine("***** Fun with Lazy Instantiation *****\n");
// Память под объект AllTracks здесь не выделяется!
MediaPlayer myPlayer = new MediaPlayer;
myPlayer.Play;
// Размещение объекта AllTracks происходит
// только в случае вызова метода GetAllTracks.
MediaPlayer yourPlayer = new MediaPlayer;
AllTracks yourMusic = yourPlayer.GetAllTracks;
Console.ReadLine;
На заметку! Ленивое создание объектов полезно не только для уменьшения количества выделений памяти под ненужные объекты. Этот прием можно также использовать в ситуации, когда для создания члена применяется затратный в плане ресурсов код, такой как вызов удаленного метода, взаимодействие с реляционной базой данных и т.п.
Настройка процесса создания данных Lazy<>
При объявлении переменной
Lazy
действительный внутренний тип данных создается с использованием стандартного конструктора:
// При использовании переменной Lazy вызывается
// стандартный конструктор класса AllTracks.
private Lazy<AllTracks> _allSongs = new Lazy<AllTracks>;
В некоторых случаях приведенный код может оказаться приемлемым, но что если класс
AllTracks
имеет дополнительные конструкторы и нужно обеспечить вызов подходящего конструктора? Более того, что если при создании переменной
Lazy
должна выполняться какая-то специальная работа (кроме простого создания объекта
AllTracks
)? К счастью, класс
Lazy
позволяет указывать в качестве необязательного параметра обобщенный делегат, который задает метод для вызова во время создания находящегося внутри типа.
Таким обобщенным делегатом является тип
System.Func<>
, который может указывать на метод, возвращающий тот же самый тип данных, что и создаваемый связанной переменной
Lazy<>
, и способный принимать вплоть до 16 аргументов (типизированных с применением обобщенных параметров типа). В большинстве случаев никаких параметров для передачи методу, на который указывает
Func<>
, задавать не придется. Вдобавок, чтобы значительно упростить работу с типом
Func<>
, рекомендуется использовать лямбда-выражения (отношения между делегатами и лямбда-выражениями подробно освещаются в главе 12).
Ниже показана окончательная версия класса
MediaPlayer
, в которой добавлен небольшой специальный код, выполняемый при создании внутреннего объекта
AllTracks
. Не забывайте,
что перед завершением метод должен возвратить новый экземпляр типа, помещенного в
Lazy<>
, причем применять можно любой конструктор по своему выбору (здесь по-прежнему вызывается стандартный конструктор
AllTracks
).
class MediaPlayer
{
...
// Использовать лямбда-выражение для добавления дополнительного
// кода, который выполняется при создании объекта AllTracks.
private Lazy<AllTracks> _allSongs =
new Lazy<AllTracks>( =>
{
Console.WriteLine("Creating AllTracks object!");
return new AllTracks;
}
);
public AllTracks GetAllTracks
{
// Возвратить все композиции.
return _allSongs.Value;
}
}
Итак, вы наверняка смогли оценить полезность класса
Lazy<>
. По существу этот обобщенный класс позволяет гарантировать, что затратные в плане ресурсов объекты размещаются в памяти, только когда они требуются их пользователю.
Резюме
Целью настоящей главы было прояснение процесса сборки мусора. Вы видели, что сборщик мусора запускается, только если не удается получить необходимый объем памяти из управляемой кучи (либо когда разработчик вызывает
GC.Collect
). Не забывайте о том, что разработанный в Microsoft алгоритм сборки мусора хорошо оптимизирован и предусматривает использование поколений объектов, дополнительных потоков для финализации объектов и управляемой кучи для обслуживания крупных объектов.
В главе также было показано, каким образом программно взаимодействовать со сборщиком мусора с применением класса
System.GC
. Как отмечалось, единственным случаем, когда может возникнуть необходимость в подобном взаимодействии, является построение финализируемых или освобождаемых классов, которые имеют дело с неуправляемыми ресурсами.
Вспомните, что финализируемые типы — это классы, которые предоставляют деструктор (переопределяя метод
Finalize
)для очистки неуправляемых ресурсов во время сборки мусора. С другой стороны, освобождаемые объекты являются классами (или структурами не
ref
), реализующими интерфейс
IDisposable
, к которому пользователь объекта должен обращаться по завершении работы с ними. Наконец, вы изучили официальный шаблон освобождения, в котором смешаны оба подхода.
В заключение был рассмотрен обобщенный класс по имени
Lazy<>
. Вы узнали, что данный класс позволяет отложить создание затратных (в смысле потребления памяти) объектов до тех пор, пока вызывающая сторона действительно не затребует их. Класс
Lazy<>
помогает сократить количество объектов, хранящихся в управляемой куче, и также обеспечивает создание затратных объектов только тогда, когда они действительно нужны в вызывающем коде.