#c# #architecture #class-design
#c# #архитектура #класс-дизайн
Вопрос:
используя C #, у меня есть класс, который содержит среди прочей метаинформации корневой узел ориентированного графа. Давайте назовем это контейнерным классом. Этот контейнер может отображаться в двух различных режимах: режиме редактора и Режиме конфигуратора. В зависимости от режима корневой узел имеет другой тип NodeEdit или NodeConfig, оба наследуются от одного и того же подкласса.
public abstract class NodeBase
{
string Name { get; set; }
...
}
public class NodeEdit : NodeBase ...
public class NodeConfig : NodeBase ...
Для контейнера я также создаю базовый класс и наследую от него:
public abstract class ContainerBase
{
NodeBase Root { get; set; }
...
}
При создании классов для Editor- и Configuratorcontainer путем наследования от ContainerBase я хочу стать типом корневого свойства определенного (унаследованного от NodeBase) типа, такого как:
public class ContainerEditor : ContainerBase
{
NodeEditor Root { get; set; }
...
}
Но я не могу изменить тип свойства, определенного в ContainerBase. Есть ли способ решить эту проблему? Я могу использовать тип BaseNode и добавить элемент NodeEditor, например
ContainerEditorInstance.Root = new NodeEditor();
поскольку тип NodeEditor наследуется от типа BaseEditor, но в классе редактора контейнеров я хочу явно разрешить только тип корневого свойства быть NodeEditor.
Я мог бы проверить это в установщике и отклонить все узлы, кроме узлов типа NodeEditor, но я хотел бы, чтобы свойство имело определенный тип, чтобы я мог обнаруживать неправильные назначения во время компиляции.
Заранее спасибо,
Фрэнк
Ответ №1:
Используйте общие:
public abstract class ContainerBase<T> where T:NodeBase
{
T Root { get; set; }
...
}
public class ContainerEditor : ContainerBase<NodeEditor>
{
...
}
Комментарии:
1. Привет, Блау, никогда не имел дела с дженериками, кроме обычного HashSet<T> и подобного использования. Думаю, что это, вероятно, будет хорошее время для работы с ними!
2. Для любого, кто рассматривает этот подход, это будет проблемой, если вам нужно выполнять операции со всеми контейнерными базами<T> или обрабатывать их как возвращаемый тип в единственном числе.
Ответ №2:
Вы можете повторно объявить его:
public class ContainerEditor : ContainerBase
{
public NodeEditor Root {
get { return (NodeEditor)base.Root; }
set { base.Root = value; }
}
...
}
Комментарии:
1. Привет, Марк, спасибо за ответ! Я уже пробовал это с повторным объявлением, но, вероятно, я сделал что-то не так. Я протестировал ваш подход, и он отлично работает! Как правило, существуют ли ситуации, когда повторное объявление должно быть предпочтительнее дженериков или наоборот, или они равны?
2. Весь этот общий материал вызывает серьезные проблемы, когда приходится использовать общий базовый класс, т. Е. Как абстрактный returntype. Итак, я попробую переопределить, поскольку это кажется менее навязчивым. По сравнению с ‘new’ и ‘override’, как переопределение работает внутри? Есть ли какая-нибудь хорошая статья об этом, которую вы можете порекомендовать? Еще раз спасибо!
3. Возвращаясь к этому старому вопросу, чтобы сказать, что это может быть очень простым решением, но его легко упустить из виду и, безусловно, является лучшим решением. После попытки использования общих, абстрактных и виртуальных классов и т.д., Это был, безусловно, самый элегантный способ решить ту же проблему, что и исходный вопрос. Это позволяет использовать класс NodeBase (т. Е. Функцию, которая принимает класс и работает с любым типом его дочерних элементов) без какой-либо путаницы.
Ответ №3:
Вы можете сделать базу контейнера универсальной:
public abstract class ContainerBase<TRoot> where TRoot : NodeBase
{
TRoot Root { get; set; }
...
}
В производном классе вы указываете тип:
public class ContainerEditor : ContainerBase<NodeEditor>
{
...
}
Ответ №4:
Я полагаю, что хорошим решением здесь будут дженерики. Итак, вы бы написали что-то вроде этого:
public class ContainerEditor<T>:ContainerBase
{
T Root {get;set;}
}
Комментарии:
1. Мне нужно было бы объявить корень в базе контейнеров, чтобы иметь возможность обращаться к нему в функциях, которым не нужно знать, является ли это редактором или конфигуратором. В любом случае дженерики звучат как хорошая идея, как показали Гуффа и Блау.