Reputation: 39690
I've got a parent class Container that may contain any kind of Node, where Node is a subclass of a generic class specific to the parent, like so:
public class ContainerBase<NodeType, ObjectType>
where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase {
}
public abstract class NodeBase<T> where T : ObjectBase {
ContainerBase<NodeBase<T>, T> container;
public NodeBase(ContainerBase<NodeBase<T>, T> owner) {
container = owner;
}
}
What I want to do is create concrete subclasses for simplicity that implement standard object types:
public class ContainerNormal : ContainerBase<NodeNormal, ObjectNormal> {
}
public class NodeNormal : NodeBase<ObjectNormal> {
//This doesn't work
public NodeNormal(ContainerNormal owner) : base(owner) { }
}
I somewhat understand why the call to the base constructor doesn't work. It's trying to convert a ContainerNormal
to a ContainerBase<NodeBase<ObjectNormal>, ObjectNormal>
which doesn't really work.
So what design pattern am I missing to make this work right? Or do I just have to take in a ContainerBase<NodeBase<ObjectNormal>,ObjectNormal>
in the constructor, even though it may not necessarily be a ContainerNormal
object?
Upvotes: 6
Views: 1662
Reputation: 6155
There is one trick you can you that I think would get what you want. You could give NodeBase an additional generic parameter that must inherit from NodeBase. This is gives a type definition that appears recursive, but the compiler has a way it works it out. Something like this should work:
public class NodeBase<T, TNode> :
where T : ObjectBase
where TNode : NodeBase<T, TNode>
{
private ContainerBase<TNode, T> container;
protected NodeBase(ContainerBase<TNode, T> owner)
{ container = owner; }
}
public class ContainerBase<NodeType, ObjectType> :
where NodeType : NodeBase<ObjectType, NodeType>
where ObjectType : ObjectBase
{
public NodeType GetItem() { ... }
}
public class NodeNormal : NodeBase<ObjectNormal, NodeNormal>
{
public NodeNormal(ContainerNormal owner) :
base(owner) { }
}
public class ContainerNormal :
ContainerBase<NodeNormal, ObjectNormal>
{
//GetItem would return NodeNormal here
}
Upvotes: 1
Reputation: 156514
In C# 4 you can accomplish this using generic interface covariance:
public class ContainerBase<NodeType, ObjectType> : IContainerBase<NodeType, ObjectType>
where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase {
}
public abstract class NodeBase<T> where T : ObjectBase {
IContainerBase<NodeBase<T>, T> container;
public NodeBase(IContainerBase<NodeBase<T>, T> owner) {
container = owner;
}
}
public class ContainerNormal : ContainerBase<NodeNormal, ObjectNormal> {
}
public interface IContainerBase<out NodeType, ObjectType>
where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase {
}
public class NodeNormal : NodeBase<ObjectNormal> {
//This doesn't work
public NodeNormal(ContainerNormal owner) : base(owner) { }
}
public class ObjectNormal : ObjectBase {}
public class ObjectBase{}
Of course, this will only work if your IContainerBase interface can avoid having any functions that would take a NodeType
as input. For example, this would work:
public interface IContainerBase<out NodeType, ObjectType>
where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase
{
NodeType NodeTypeProp {get;}
}
... but this wouldn't:
public interface IContainerBase<out NodeType, ObjectType>
where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase
{
NodeType NodeTypeProp {get;set;} // "set" not allowed on "out" type
}
One more note: You notice how I had to name the property "NodeTypeProp" instead of "NodeType"? That's because we're not following C# naming conventions. You should prefix generic type names with "T":
public interface IContainerBase<out TNodeType, TObjectType>
where TNodeType : NodeBase<TObjectType> where TObjectType : ObjectBase
Upvotes: 1
Reputation: 37719
Explore: Covariance and contravariance, but this should work:
public class Container<TNode, T> where TNode : Node<T> { }
public abstract class Node<T>
{
Container<Node<T>, T> container;
public Node(Container<Node<T>, T> owner)
{
this.container = owner;
}
}
public class ContainerNormal<T> : Container<Node<T>, T> { }
public class NodeNormal<T> : Node<T>
{
public NodeNormal(ContainerNormal<T> container)
: base(container)
{
}
}
public class ContainerNormal : ContainerNormal<string> { }
public class NodeNormal : NodeNormal<string>
{
public NodeNormal(ContainerNormal container)
: base(container)
{
}
}
Upvotes: 3
Reputation: 8885
You can can change the definition of Container normal as follows:
public class ContainerNormal : ContainerBase<NodeBase<ObjectNormal>, ObjectNormal>
{
}
Upvotes: 0