3) Жизненный цикл объектов (сборка мусора) / Привязка данных в WPF

Жизненный цикл объектов
Все типы платформы .NET делятся на ссылочные типы и типы значений. Переменные типов значений создаются в стеке, их время жизни ограничено тем блоком кода, в котором они объявляются. Например, если переменная типа значения объявлена в некотором методе, то после выхода из метода память в стеке, занимаемая переменной, автоматически освободится. Переменные ссылочного типа (объекты) располагаются в динамической памяти – управляемой куче (managed heap). Размещение объектов в управляемой куче происходит последовательно. Для этого CLR поддерживает указатель на свободное место в куче, перемещая его на соответствующее количество байтов после выделения памяти очередному объекту

Алгоритм сборки мусора
Если новый объект требует для размещения больше памяти, чем имеющийся свободный объем, CLR запускает процесс, называемый сборка мусора (garbage collection). На первом этапе сборки мусора строится граф используемых объектов . Отправными точками в построении графа являются корневые объекты . Это объекты следующих категорий:
– локальная переменная или аргумент выполняемого метода (а также всех методов в стеке вызова);
– статическое поле;
– объект в очереди завершения (этот термин будет разъяснён позже).
При помощи графа используемых объектов выясняется реально занимаемая этими объектами память. Затем происходит дефрагментация кучи – используемые объекты перераспределяются так, чтобы занимаемая ими память составляла единый блок в начале кучи. После этого сборка мусора завершается, и новый объект размещается в управляемой куче.
При размещении и удалении объектов CLR использует ряд оптимизаций. Во-первых, объекты размером 85000 и более байтов размещаются в отдельной управляемой куче больших объектов (Large Object Heap). При сборке мусора данная куча не дефрагментируется, так как копирование больших блоков памяти снижает производительность. Во-вторых, в управляемой куче для малых объектов выделяется три поколения – Gen0, Gen1 и Gen2. Вначале все объекты относятся к поколению Gen0. После первой сборки мусора «пережившие» её объекты переходят в поколение Gen1. В дальнейшем сборка мусора будет работать с объектами поколения Gen1, только если освобождение памяти в Gen0 дало неудовлетворительный результат. Если в Gen1 произошла сборка мусора, то «пережившие» её объекты переходят в поколение Gen2. Отметим, что куча больших объектов всегда рассматривается как куча объектов поколения Gen2.
Сборщик мусора представлен статическим классом System.GC , который обладает несколькими полезными методами (приведён неполный список):
1. Collect() – вызывает принудительную сборку мусора в программе.
2. GetGeneration() – возвращает номер поколения для указанного объекта;
3. SuppressFinalize() – подавляет вызов финализатора для объекта;
4. WaitForPendingFinalizers() – приостанавливает текущий поток выполнения до тех пор, пока не будут выполнены все финализаторы освобождаемых объектов.
Финализаторы и интерфейс IDisposable

Платформа .NET предлагает подход, обеспечивающий автоматическое освобождение ресурсов при уничтожении объекта. Тип System.Object содержит виртуальный метод Finalize(). Класс (но не структура!) может переопределить этот метод для освобождения неуправляемых ресурсов. Объект такого класса обрабатывается особо при размещении в куче и при сборке мусора. При размещении ссылка на объект запоминается в специальной внутренней структуре CLR. При сборке мусора эта ссылка перемещается в очередь завершения (freachable queue). Затем в отдельном программном потоке у объектов из очереди завершения происходит вызов метода Finalize() , после этого ссылка на объект удаляется из очереди завершения.
Язык C# не позволяет явно переопределить в пользовательском классе метод Finalize() . Вместо этого в классе описывается специальный финализатор. Имя финализатора имеет вид ~имя-класса (), он не имеет параметров и модификаторов доступа (считается, что у него модификатор доступа protected ). При наследовании в финализатор класса-наследника автоматически подставляется вызов финализатора класса-предка.
Вызов метода Finalize() является недетерминированным. Для освобождения управляемых ресурсов (т. е. ресурсов платформы .NET) программист может описать в классе некий метод, который следует вызывать вручную, когда ресурс больше не нужен. Для унификации данного решения предлагается интерфейс IDisposable с единственным методом Dispose(). Если класс или структура реализуют этот интерфейс, Dispose() содержит код освобождения управляемых ресурсов.
Язык C# имеет специальный оператор using, который гарантирует вызов метода Dispose() для заданных переменных в конце блока кода. Синтаксис оператора using следующий:

using (получение-ресурса) вложенный-оператор
Здесь получение-ресурса означает один из вариантов.
1. Объявление и инициализацию локальной переменой (или списка переменных). Тип переменной должен реализовывать IDisposable. Такая переменная в блоке using доступна только для чтения.
2. Выражение, значение которого имеет тип, реализующий IDisposable.

Слабые ссылки
Слабая ссылка (weak reference) – особый вид ссылки на объект в системах со сборкой мусора. Если на объект имеются только слабые ссылки, он рассматривается алгоритмом сборки мусора как подлежащий удалению.
В .NET слабые ссылки представлены классами System.WeakReference и System.WeakReference<T> . Конструктор класса принимает объект, на который создаётся слабая ссылка. Свойство Target указывает на этот объект или имеет значение null , если объект был удалён сборщиком мусора.

Привязка данных
Привязка данных (data binding) – это отношение, которое используется для извлечения информации из источника данных и установки свойства зависимостей в целевом объекте . Целевой объект обычно является элементом управления. Источником данных может быть произвольный объект .NET. Привязка способна (при определённых условиях) автоматически обновлять целевое свойство при изменении данных источника.
Очевидно, что при определении привязки нужно указать целевое свойство, источник данных и правило извлечения информации из источника. Кроме этого, можно выполнить настройку следующих параметров:
1. Направление привязки. Привязка может быть однонаправленной (целевое свойство меняется при изменении данных источника), двунаправленной (изменения в источнике и целевом объекте влияют друг на друга) и от цели к источнику (источник данных обновляется при изменении целевого свойства).
2. Условие обновления источника. Если привязка двунаправленная или от цели к источнику, можно настроить момент обновления источника данных.
3. Конвертеры значений. Привязка может выполнять автоматическое преобразование данных при их перемещении от источника к целевому объекту и наоборот.
4. Правила проверки данных. Привязка может включать правила проверки данных на корректность и поведение, выполняемое при нарушении этих правил.
В WPF привязка данных описывается либо декларативно, либо в коде. В любом случае используется объект класса System.Windows.Data.Binding (унаследованный от MarkupExtension).
Класс System.Windows.Data.BindingOperations предоставляет набор статических методов для управления привязкой в коде. В частности, метод GetBinding() позволяет получить привязку для указанного целевого объекта и его свойства зависимостей, метод SetBinding() устанавливает привязку, а метод ClearBinding() удаляет привязку.

Источники и поставщики данных
Источник данных для привязки может быть задан при помощи следующих свойств объекта Binding: ElementName, Source, RelativeSource. Эти свойства являются взаимоисключающими – установив одно из них, остальные следует сбросить, иначе при выполнении привязки генерируется исключение. 
Строковое свойство ElementName удобно использовать, чтобы определить в качестве источника данных элемент, который принадлежит одному логическому дереву вместе с целевым объектом.
Свойство Source даёт возможность указать в качестве источника привязки произвольный объект.
Свойство привязки RelativeSource позволяет задать источник данных на основе отношения к целевому объекту.
Если при описании привязки не задано ни одно из свойств ElementName, Source, RelativeSource, источник данных определяется на основе значения свойства DataContext целевого объекта. Если у целевого объекта это свойство не установлено, будет выполнен поиск по дереву элементов для нахождения первого родительского DataContext, не равного null.
Технология WPF поддерживает механизм поставщиков данных (data providers), чтобы упростить декларативную настройку доступа к источникам данных. В настоящее время имеется два поставщика: ObjectDataProvider получает информацию, вызывая заданный метод, а XmlDataProvideг доставляет данные непосредственно из источника XML. Все поставщики данных унаследованы от класса System.Windows.Data.DataSourceProvider.

Обновление данных и направление привязки
Ранее было отмечено, что привязка способна автоматически обновлять целевое свойство при изменении источника данных. Чтобы реализовать этот функционал, можно поступить одним из следующих способов:
1. Сделать свойство источника данных свойством зависимостей.
2. Генерировать при изменении источника событие Имя-Свойства Changed.
3. Реализовать в источнике интерфейс INotifyPropertyChanged (пространство имён System.ComponentModel), содержащий событие PropertyChanged. Это событие нужно генерировать при изменении данных, указывая имя свойства.
Для того чтобы изменения в целевом свойстве попадали в источник данных, нужно установить режим привязки в BindingMode.TwoWay или BindingMode.OneWayToSource.

Конвертеры значений
Конвертер значений отвечает за преобразование данных при переносе из источника в целевое свойство и за обратное преобразование в случае двунаправленной привязки.
Для создания конвертера значений требуется выполнить четыре шага.
1. Создать класс, реализующий интерфейс IValueConverter.
2. Применить к классу атрибут [ValueConversion ] и специфицировать исходный и целевой типы данных (необязательный шаг).
3. Реализовать метод Convert(), выполняющий преобразование данных от источника к целевому объекту.
4. Реализовать метод ConvertBack(), делающий обратное преобразование.

Проверка данных
Для привязки, которая передаёт информацию в источник данных (режимы BindingMode.TwoWay и BindingMode.OneWayToSource) можно выполнить проверку данных перед помещением их в источник. Если данные не проходят проверку, то источник не обновляется.
Первый способ организации проверки заключается в создании и применении проверочных правил . Каждое проверочное правило – это наследник класса System.Windows.Controls.ValidationRule с перекрытым методом Validate() . Этот метод получает проверяемое значение и информацию о культуре, а возвращает объект ValidationResult с результатом проверки.
Второй способ организации проверки основан на генерации исключительной ситуации в методе установки свойства источника данных
Исключительные ситуации, возникающие при передаче информации в источник, отлавливаются при помощи специального встроенного правила ExceptionValidationRule.
Третий способ организации проверки основан на реализации источником данных интерфейса System.ComponentModel.IDataErrorInfo . Интерфейс IDataErrorInfo содержит два элемента: строковое свойство Error и строковый индексатор с ключом-строкой. Свойство Error – это общее описание ошибок объекта. Индексатор принимает имя свойства и возвращает соответствующую детальную информацию об ошибке. Ключевая идея в том, что вся логика обработки ошибок централизована в индексаторе.
Четвёртый вариант организации проверки основан на реализации источником данных интерфейса System.ComponentModel.INotifyDataErrorInfo. Этот интерфейс похож на гибрид интерфейсов INotifyPropertyChanged и IDataErrorInfo . Его булево свойство HasErrors указывает на наличие ошибок объекта. Метод GetErrors() позволяет получить список ошибок по строке с именем свойства. Событие ErrorsChanged необходимо генерировать при изменении списка ошибок.