C#中的通用父类和通用子类

6

我有一个父类Container,它可以包含任何类型的节点Node,其中Node是特定于父类的通用类的子类,如下所示:

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;
    }
}

我想做的是创建具体的子类,以实现标准对象类型的简化:
public class ContainerNormal : ContainerBase<NodeNormal, ObjectNormal> {
}

public class NodeNormal : NodeBase<ObjectNormal> {
    //This doesn't work
    public NodeNormal(ContainerNormal owner) : base(owner) { }
}

我有些理解为什么调用基础构造函数不起作用。 它试图将ContainerNormal转换为ContainerBase<NodeBase<ObjectNormal>, ObjectNormal>,但实际上这并不起作用。

那么,我缺少哪种设计模式才能使其正常工作? 或者我是否只需在构造函数中输入ContainerBase<NodeBase<ObjectNormal>,ObjectNormal>,即使它可能不是一个ContainerNormal对象?

4个回答

3

了解一下:协变和逆变,但是这应该可以工作:

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)
    {
    }
}

这似乎真的可以工作,尽管它最终会使命名空间有些混乱... - Ed Marty
经过检查,似乎存在与Can Gencer的答案相同的问题,即尝试从ContainerNormal获取NodeNormal将返回一个Node<String>,它几乎相同,但并非完全相同。 - Ed Marty

1
在C# 4中,您可以使用通用接口协变来实现这一点:
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{}

当然,这只有在您的IContainerBase接口中避免使用任何以NodeType作为输入的函数时才有效。例如,以下代码可以正常工作:

public interface IContainerBase<out NodeType, ObjectType>
    where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase 
{
    NodeType NodeTypeProp {get;}
}

...但这样做不会:

public interface IContainerBase<out NodeType, ObjectType>
    where NodeType : NodeBase<ObjectType> where ObjectType : ObjectBase 
{
    NodeType NodeTypeProp {get;set;} // "set" not allowed on "out" type
}

另外需要注意的是:你是否注意到我不得不将属性命名为“NodeTypeProp”而不是“NodeType”?这是因为我们没有遵循C#的命名规范。你应该在泛型类型名称前加上“T”前缀:
public interface IContainerBase<out TNodeType, TObjectType>
    where TNodeType : NodeBase<TObjectType> where TObjectType : ObjectBase 

不幸的是,我被困在 .NET 3.5 上,否则这听起来就是最简洁的解决方案。 - Ed Marty
@Ed Marty:真遗憾。我过去为了处理这种情况,不得不紧密地将所有这些类耦合在一起。因此,NodeBase的通用参数将包括ContainerBase类型,而ContainerBase的通用参数将包括NodeBase类型。这很丑陋。 - StriplingWarrior

1

有一个技巧可以实现你想要的效果。你可以给NodeBase添加一个额外的泛型参数,该参数必须继承自NodeBase。这会产生一种递归的类型定义,但编译器有一种解决方法。以下代码应该可以实现:

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
}

0
您可以按照以下方式更改容器正常的定义:

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

这将解决编译错误,但它并没有解决问题,例如除了NodeNormals之外的其他子项可以存储在ContainerNormals中。如果我尝试从ContainerNormal中取出一个子项,它将是NodeBase<ObjectNormal>类型,而不是NodeNormal类型。因此,如果我想向NodeNormal添加任何方法并在从ContainerNormal获取的对象上调用它们,那么在没有强制转换的情况下无法正常工作。 - Ed Marty
在这种情况下,您需要一个中间类,该类继承自ContainerBase<NodeBase<ObjectNormal>, ObjectNormal>,例如ContainerNormal<ObjectType>:ContainerBase<NodeBase<ObjectType>, ObjectType>。我认为这会变得混乱,突然之间你将不得不处理两种类型的ContainerNormals。您可以向ContainerNormal添加属性以直接获取ObjectNormal,并添加一种方法来验证它始终获取ObjectNormals。有点混乱,但我认为从API的角度来看,这样做会更容易使用。 - Can Gencer
如果您不打算实现从NodeBase<ObjectNormal>继承的多个类型,那么这应该不是问题。 - Can Gencer
除非我计划这样做,即使我不打算这样做,我仍然需要将类型强制转换为正确的类型。 - Ed Marty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接