Записная книжка разработчика

Мои проекты

Использование Reflection для динамического создания компонентов в C#

| Comments

Пусть есть следующая задача: нужно, чтобы форма приложения создавалась динамически в run-time, на основе конфигурационного файла, например, в формате xml. Подобные задачи могут решаться разными способами, например, с помощью XAML (основан на XML, технология Microsoft), или QML (основан на Java Script, технология Qt). Ниже приводится пример реализации такого подхода на основе XML и механизма Reflection, доступного в С#.

Итак, Reflection - это часть платформы .Net, позволяющая использовать метаданные сборки в run-time. Это означает, что мы можем создать экземпляр класса, зная его имя, и присвоить значения его свойствам, зная их имена и значения. Помимо перечисленного, Reflection включает в себя еще много возможностей, но пока нам хватит этих.

Далее под катом

Итак, нам нужен конфигурационный файл.

Программа будет состоять из двух основных частей: одна читает xml-файл и строит по нему структуру данных. Ниже приведен фрагмент программы, представляющий структуру данных:

Здесь:
класс PropDescriptor - описание свойства объекта, пара имя-значение, оба поля являются строками,
класс ClassPropertyDescriptor описание свойства объекта, которое в свою очередь является объектом. Здесь name - имя свойства, className - имя класса, представляющего свойство, parameterList - список аргументов конструктора класса (см. ниже), propertyList - список свойств объекта, classPropertyList - список свойств объекта, которые, в свою очередь, являются объектами.
класс ParameterDescriptor - описание аргумента конструктора объекта. Состоит из имени аргумента name, имени типа аргумента typeName и строкового представления значения аргумента value.
класс ControlDescriptor - описание контрола. Состоит из имени класса typeName и трех списков: propertyList - список свойств, classPropertyList - список свойств, являющихся классами, childControlList - список вложенных контролов.

В этой структуре данных имеется два типа свойств объекта: "обычные" свойства, имеющие один из базовых типов, и свойства, представляющие собой объект. Для таких свойств нужно, соответственно, вызывать конструктор с определенными параметрами. Например, контрол Label имеет свойство Text, имеющее тип string, и свойство Location, имеющее тип Point. Point является классом, в его конструктор должны передаваться параметры X и Y типа Int32 (вернее, Point является структурой, но в данном контексте разница не имеет значения).

Основная часть программы занимается построением контролов по имеющимся их описанием, и делает это с помощью Reflection.

Для того, чтобы создать в run-time контрол через Reflection, используется следующий код:

Здесь nameSpace - пространство имён, по умолчанию System.Windows.Forms. Пространство имен по умолчанию может быть изменено в xml-файле, если имя класса контрола содержит точку, то имя пространства имен по умолчанию не подставляется, используется имя, указанное для данного контрола.

Значения свойств контрола присваиваются следующим образом:

Здесь подразумевается, что свойство может иметь "длинное" имя вида name1.name2.name3... Тип, в который происходит преобразование строкового представления значения свойства берётся из структуры PropertyInfo (это тоже часть Reflection).
Однако если попытаться таки образом присвоить значение, например, Location.X, ничего не получено, так как структуру Location нужно создать, вызвав её конструктор, причем сразу с параметрами X и Y.

Свойства такого вида конструируются так:

 

Здесь преобразование типов строкового представления значения аргумента конструктора происходит в тот тип, который мы должны явно указать в xml. Так как у класса может быть множество конструкторов с различными сигнатурами, автоматически выбрать из них нужный было бы затруднительно. В приведенном коде предусмотрено преобразование типа только в типы Int32 и string, для использования других типов этот код нужно будет соответственно дополнять.

Исходники на github: https://github.com/arktur04/ComponentAdapterTest.git