【问题标题】:Is there a way to declare members of an abstract base class that are the type of the derived class?有没有办法声明派生类的类型的抽象基类的成员?
【发布时间】:2018-04-26 21:07:23
【问题描述】:

假设我有一个抽象基类,我想在其中声明与派生自该基类的类的类型匹配的成员。

public abstract class BaseClass
{
    protected BaseClass parent;
}

public class DerivedClass1 : BaseClass
{
    // parent could be of type DerivedClass2
}

public class DerivedClass2 : BaseClass
{
    // parent could be of type DerivedClass1
}

这不起作用,因为每个派生类中的parent 字段可以是从BaseClass 派生的任何内容。我想确保DerivedClass1 中的parent 字段只能是DerivedClass1 类型。所以我在想也许我应该使用泛型。

public abstract class BaseClass<T> where T : BaseClass<T>
{
    protected T parent;
}

这可能看起来令人困惑和循环,但它确实可以编译。它基本上是说parentT 类型,它必须派生自通用BaseClass。所以现在派生类可以如下所示:

public class DerivedClass : BaseClass<DerivedClass>
{
    // parent is of type DerivedClass
}

问题是当我声明DerivedClass 时,我必须自己强制执行类型匹配。没有什么能阻止某人做这样的事情:

public class DerivedClass1 : BaseClass<DerivedClass2>
{
    // parent is of type DerivedClass2
}

C# 是否有办法做到这一点,以便在基中声明的成员的类型肯定与派生类型匹配?

我认为这类似于这个 C++ 问题试图提出的问题:Abstract base class for derived classes with functions that have a return type of the derived class

【问题讨论】:

  • 这称为 CRTP。
  • 如果您希望父属性具有特定类型,那么为什么您需要担心该类型从哪个基类继承?您也不需要在抽象类中声明此属性。您只需要在适当的类中创建属性。
  • 恐怕你的问题不清楚。我的意思是,很清楚你想要什么。但是,您现在应该已经发现 C# 没有提供这种约束。更重要的是,您的问题提供了关于 why 您认为此约束有用的零细节。如果类型参数T 实际上与声明类型不同,为什么这对您很重要?这将如何影响基类或派生类的实现?在这样的限制下,你不能做什么?请提供有关您的期望的更多详细信息。
  • 您的问题的答案是“否”。
  • 我担心包含更多关于我正在尝试创建的程序的细节会使我已经很长的问题的长度增加一倍。

标签: c# generics inheritance types abstract-base-class


【解决方案1】:

如果我正确理解您的要求,您有一系列具有继承关系的类,并且您希望将它们排列在一个树形结构中,其中每个实例都有一个相同类型且仅属于相同类型的父级。这是一个有趣的问题。

稍微琢磨了一下,我建议你把需求分成两个平行但相关的对象图,这样你就有了

  1. 一组具有继承关系的类
  2. 一组类,可以包含第一组中的任何类,并且具有类型严格的父项和子项。

首先,让我们声明第一组相互继承的类。暂时忽略Node 位。

public class BaseClass 
{
    public Node ContainerNode { get; set; }
}

public class DerivedClass1 : BaseClass
{
}

public class DerivedClass2 : BaseClass
{
}

这些类不能做太多,但这只是一个例子。

现在让我们设置另一组可以参与树的类。树中的每个元素都称为Node

//Basic node
public class Node
{
}

//A node that can contain a T (which must be a BaseClass or derived from one)
public class Node<T> : Node where T : BaseClass
{
    public T Parent { get; set; }
    public T This { get; set; }

    public Node(T innerClass) 
    {
        this.This = innerClass;
        innerClass.ContainerNode = this;
    }
}

现在我们拥有了执行您所寻求的类型安全所需的一切。我们可以像这样在继承层次结构中创建类:

    var child1 = new Node<DerivedClass1>(new DerivedClass1());
    var parent1 = new Node<DerivedClass1>(new DerivedClass1());
    child1.Parent = parent1.This;

让我们看看如果我们错误地混淆了 DerivedClass1 和 DerivedClass2 会发生什么:

    var child2 = new Node<DerivedClass2>(new DerivedClass2());
    var parent2 = new Node<DerivedClass1>(new DerivedClass1()); //Oops
    child2.Parent = parent2.This;  //Does not compile

如您所见,Parent 属性现在是类型安全的。

现在所有的东西 ^^^^ 看起来有点乱,所以让我们通过添加一些辅助方法来清理它。

public class Node
{
    public T GetParent<T>() where T : BaseClass
    {
        return ((Node<T>)this).Parent;
    }

    static public Node<T> Create<T>(T innerClass) where T : BaseClass
    {
        return new Node<T>(innerClass);
    }

    static public T GetParent<T>(T child) where T: BaseClass
    {
        return child.ContainerNode.GetParent<T>();
    }

    static public implicit operator T (Node<T> input)
    {
        return input.This;
    }
}

现在,由于编译器可以推断 &lt;T&gt; 参数,我们的声明更加简洁:

    var child1 = Node.Create(new DerivedClass1());
    var parent1 = Node.Create(new DerivedClass1());
    child1.Parent = parent1;

任何派生类都很容易找到自己的父类:

public class DerivedClass1 : BaseClass
{
    protected DerivedClass1 Parent
    {
        get
        {
            return Node.GetParent(this);  //This is type safe!
        }
    }
}

对这一切的一个反对意见是,您不希望编码人员处理这个 Node 层。好吧,他们没有,因为我们设置了一个隐式转换:

    Node<DerivedClass1> a = Node.Create(new DerivedClass1());
    DerivedClass1 b = a;  //Works!!! And is type-safe.

Full working code on DotNetFiddle.

【讨论】:

  • 非常令人印象深刻!令人担忧的是,ContainerNodeParentThis 这样的成员是公开的,但也许它们可以是内部的。
  • 同意。或者您可以将它们设为public readonly 并通过构造函数参数设置它们。如果它们是不可变的,则问题不大。
猜你喜欢
  • 1970-01-01
  • 2021-07-24
  • 1970-01-01
  • 2023-03-20
  • 2017-06-19
  • 2021-01-12
  • 2015-04-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多