Ed Marty
Ed Marty

Reputation: 39690

Generic parent of generic child C#

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

Answers (4)

Gideon Engelberth
Gideon Engelberth

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

StriplingWarrior
StriplingWarrior

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

eulerfx
eulerfx

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

Can Gencer
Can Gencer

Reputation: 8885

You can can change the definition of Container normal as follows:

public class ContainerNormal : ContainerBase<NodeBase<ObjectNormal>, ObjectNormal>
{
}

Upvotes: 0

Related Questions