Как XAML интерпретируется и выполняется во время выполнения?

Что происходит внутри между загрузкой XAML (или BAML) и получением корневого объекта (например, Window)?

Вначале я вспоминаю, что Reflecton используется для создания объектов, их свойств и т.д. Но, может быть, я ошибаюсь?

Может кто-то может объяснить, как анализируется и выполняется XAML/BAML во время выполнения или дает ссылку на хорошую статью с объяснением?

Чтобы сделать мой вопрос немного более понятным, обсудим короткий пример:

<Button Margin="10">OK</Button>

Итак, анализатор видит, что нужно создать объект Button, чтобы его свойство Margin было установлено равным 10, и его содержимое должно быть установлено на "OK". Как это делается? Используя Reflection (плюс TypeConverters и т.д.)?

Ответы

Ответ 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 является свойством зависимости, он использует API SetValue() для установки значения свойства; если это свойство 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 как мощный формат сериализации и персистентности, а не просто язык для создания пользовательских интерфейсов.