Ответ 1
В ваших файлах .xaml.cs
вы можете заметить, что классы, поддерживающие ваши скомпилированные файлы XAML, помечены как классы partial
. Задача сборки XAML создает второй файл .cs
с другим сектором частичного класса, содержащим реализацию метода IComponentConnector.InitializeComponent()
, который вызывается конструктором по умолчанию в коде позади. Этот метод в основном проходит через XAML (который фактически находится в форме BAML в этой точке) и использует его для "исправления" вновь созданного объекта, в отличие от создания нового объекта из источника XAML, что и произойдет, если вы должны были использовать XamlReader
для загрузки или анализа объекта.
Итак, когда вы создаете экземпляр нового скомпилированного объекта XAML (например, a UserControl
), любой код предшествует вызову InitializeComponent()
в конструкторе. Затем все свойства и обработчики событий, установленные в файле XAML, будут обработаны во время вызова InitializeComponent()
, после чего конструктор возобновится. Это может быть полезно знать, поскольку вы можете захотеть убедиться, что определенные свойства будут установлены до или после обработки файла XAML.
В отношении того, как анализируется XAML, он по существу считывается как поток узлов XAML, представляющих назначения свойств, объявления объектов и т.д., которые выполняются в порядке обслуживания в System.Xaml
. Этот поток node основан на общей объектной модели, которая может быть построена из потока BAML, XML-документа (например, файла .xaml
), другого экземпляра объекта и т.д. BAML, будучи более компактным, чем XML-формат, как правило, быстрее разбирается.
Добавление: В добавленном примере вы спрашиваете, как парсер видит, что объект Button
должен быть создан, и установить Margin
. Короткий ответ: это зависит. В частности, это зависит от контекста схемы, используемого для чтения потока XAML.
Анализатор XAML использует свою собственную систему типов, из которых есть как минимум две реализации:
- Стандартная система CLR на основе отражения и
System.ComponentModel
; - Система типа WPF, которая расширяет # 1, включая специальную поддержку свойств зависимостей и маршрутизируемых событий.
Это, основываясь на моем воспоминании XAML Language Spec, примерно то, что происходит:
- Парсер XAML встречает
StartObject
node для типаButton
, который (для стандартных сопоставлений пространства имен WPF) разрешается доSystem.Windows.Controls.Button
. Это говорит парсеру, что ему нужно создать экземпляр объектаButton
, который он делает, вызывая его конструктор по умолчанию через отражение. - Следующий node в потоке - это
StartMember
node, с именем членаMargin
. Модель типа для контекста схемы WPF разрешит это для свойства зависимостейMargin
. - Далее появится
Value
node, который сообщает синтаксическому анализатору установить значение"10"
(строка). Парсер видит, что тип свойстваThickness
несовместим со строковым значением. Он консультирует свою систему типов, чтобы узнать, существует ли атрибут[ValueSerializer]
в свойствеMargin
, который может быть использован для преобразования строки; такой атрибут не существует. Он проверяет свойство для атрибута[TypeConverter]
; опять же, он не находит ничего. Он ищет атрибут[TypeConverter]
для самого типаThickness
и находит его, который инструктирует его использоватьThicknessConverter
для преобразования строкового значения вThickness
. Он делает это. ПосколькуMargin
является свойством зависимости, он использует APISetValue()
для установки значения свойства; если это свойство CLR, оно будет использовать отражение илиPropertyDescriptor
. - An
EndMember
node сообщает синтаксическому анализатору прекратить назначение свойства. - Парсер встречает
Value
node с содержимым"OK"
. Парсер знает, что он создает сложный объект, поэтому контент не может представлять весь объект. Он ищет атрибут[ContentProperty]
наButton
и его супертипы; он находит один наContentControl
, который указывает, что значение должно использоваться для установки свойстваContent
(которое получает разрешение на соответствующее свойство зависимостей).Content
являетсяobject
, поэтому он непосредственно присваивает значениеstring
(опять же, используяSetValue()
). - Следующий node -
EndObject
, который сообщает парсеру, что он завершил обработку объектаButton
.
Обратите внимание, что я использовал термин "парсер" для упрощения. Честно говоря, ничто из этого не происходит на этапе синтаксического анализа (если даже существует "синтаксический" этап). То, что вы можете себе представить как этап "синтаксического анализа", - это просто построение потока узлов XAML. Создание и/или совокупность объявленного объекта (ов) фактически происходит путем подачи этого потока в XamlObjectWriter
, который представляет собой просто реализацию XamlWriter
, которая записывает узлы XAML в объект (в отличие от документа XML или BAML). На высоком уровне происходит только две вещи:
-
XamlReader
преобразует что-то в поток узлов XAML. -
XamlWriter
преобразует поток узлов XAML во что-то.
В случае скомпилированного ресурса XAML задача сборки времени компиляции выводит вывод XamlXmlReader
в BamlWriter
для "компиляции" XAML. Во время выполнения вход BamlReader
передается в XamlObjectWriter
для создания или "исправления" корневого объекта.
Как только вы все это поймете, вы можете начать распознавать XAML как мощный формат сериализации и персистентности, а не просто язык для создания пользовательских интерфейсов.