Динамическое приведение в C # - PullRequest
1 голос
/ 12 июля 2009

Изобразите следующую ситуацию. У меня есть XML-документ следующим образом,

<Form>
    <Control Type="Text" Name="FirstName" />
    <Control Type="DateTime" Name="DateOfBirth" />
    <Control Type="Text" Name="PlaceOfBirth" />
</Form>

У меня есть абстрактный класс Control с единственным абстрактным методом Process, который принимает один параметр HttpRequest. У меня также есть два других класса, которые являются производными от элемента управления и называются TextControl и DateTimeControl. И Text, и DateTime переопределяют метод Process, предоставляя свои собственные реализации.

У меня также есть класс Form, в котором есть метод Process, принимающий один параметр типа HttpRequest, и конструктор, который принимает один параметр типа XmlDocument.

Создается новый экземпляр Form, и вышеуказанный Xml передается через параметр XmlDocument (как мы получаем из строки в XmlDocument не имеет значения). Затем я вызываю метод Process в только что созданном экземпляре формы и передаю параметр типа HttpRequest, как и ожидалось.

Пока все хорошо. Теперь на вопрос.

Чтобы сделать обработку элементов управления расширяемой, я бы хотел иметь возможность сопоставлять классы с типами элементов управления.

например.

Form.RegisterControl("Text", Text)
Form.RegisterControl("DateTime", DateTimeControl)

В методе Process формы Form я хотел бы использовать его для каждого узла Control в документе (как это сделать снова не имеет значения) и создавать экземпляр класса, который соответствует его типу на основе классов, зарегистрированных нашим методом RegisterControl , На этом этапе я мог бы указать, что они являются производными от Control, но не могли явно указать их тип. Поскольку они оба получены из Control, я хочу вызвать метод Process, который, как я знаю, будет реализован.

Это вообще возможно? Если так, как бы я поступил об этом?

1 Ответ

4 голосов
/ 12 июля 2009

(Этот ответ в некотором роде два разных ответа, в зависимости от значения вашего вопроса. Надеюсь, одна часть его полезна, в любом случае:)


Вероятно, лучший способ - передать аргумент, который является функцией, которую можно использовать для создания нового элемента управления в нужное время. Если вы используете C # 3, это так просто:

Form.RegisterControl("Text", () => new Text())

В качестве альтернативы, вы можете сделать его универсальным методом с двумя ограничениями: один для элемента управления и один для конструктора без параметров.

public void RegisterControl<T>(string name) where T : Control, new()

затем назовите его с:

Form.RegisterControl<Text>("Text");
Form.RegisterControl<DateTimeControl>("DateTime");

RegisterControl должен был бы помнить typeof(T) в любом хранилище, которое он использует, но по крайней мере тогда можно было бы с достаточной уверенностью предположить, что Activator.CreateInstance(Type) сработает позже - и у вас будет проверка во время компиляции.

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

Form.RegisterControl("Text", data => new Text(data));

Вы не можете выразить конструктор такого типа с общим ограничением, и в любом случае это будет относительно трудно вызвать позже.


РЕДАКТИРОВАТЬ: Возможно, что и я, и Мердад неправильно истолковали вопрос. У вас действительно есть различные перегрузки RegisterControl в зависимости от типа элемента управления? Если это так, то единственные способы прямого вызова правильной перегрузки во время выполнения - это либо использовать отражение, либо использовать динамическую типизацию в C # 4.

Другая альтернатива - использовать двойную диспетчеризацию - поместить в сам элемент управления метод, который знает, как зарегистрироваться в форме. Это будет указано в интерфейсе или базовом классе, но переопределено в конкретных подклассах. Итак, ваш код, который на данный момент:

Form.RegisterControl("Text", control);

станет:

control.RegisterWith(Form, "Text");

Чтобы мог затем вызвать корректную перегрузку без проблем.

В основном вам нужно помнить, что разрешение перегрузки выполняется во время компиляции, но разрешение переопределения выполняется во время выполнения - поэтому, если вы хотите создать что-то динамическое, попробуйте применить к нему полиморфизм.

...