为什么可以';t基类规范的含义递归地依赖于C#中的基类规范本身

本文关键字:基类 递归 范本身 依赖于 为什么 | 更新日期: 2023-09-27 18:19:44

以下C#代码不编译:

public class A
{
    public interface B { }
}              
public class C
    : A,
      C.B // Error given here: The type name 'B' does not exist in the type 'C'.
{ }
public class D : C.B // Compiles without problems if we comment out 'C.B' above.
{ }

根据C#4.0规范(第10.1.4.1段),这种行为是正确的:

在确定类B的直接基类规范A的含义时,暂时假定B的直接类为对象。直观地说,这确保了基类规范的含义不能递归地依赖于它自己。

我的问题是:为什么这种行为不被允许?

Intellisense对此没有问题——尽管我知道这并不能说明什么,但在目睹了Visual Studio崩溃之后,Intellisense试图理解一些带有变体泛型的邪恶类组合。

在互联网上搜索规范中的上述引用没有任何结果,所以我猜这还没有在任何地方被提及。

我为什么在乎?我设计了以下代码:

// The next three classes should really be interfaces,
// but I'm going to override a method later on to prove my point.
// This is a container class, that does nothing except contain two classes.
public class IBagContainer<Bag, Pointer>
    where Bag : IBagContainer<Bag, Pointer>.IBag
    where Pointer : IBagContainer<Bag, Pointer>.IPointer
{
    // This could be an interface for any type of collection.
    public class IBag
    {
        // Insert some object, and return a pointer object to it.
        // The pointer object could be used to speed up certain operations,
        // so you don't have to search for the object again.
        public virtual Pointer Insert(object o) { return null; }
    }
    // This is a pointer type that points somewhere insice an IBag.
    public class IPointer
    {
        // Returns the Bag it belongs to.
        public Bag GetSet() { return null; }
    }
}
// This is another container class, that implements a specific type of IBag.
public class BinarySearchTreeContainer<Tree, Node> : IBagContainer<Tree, Node>
    where Tree : BinarySearchTreeContainer<Tree, Node>.BinarySearchTree
    where Node : BinarySearchTreeContainer<Tree, Node>.BinarySearchTreeNode
{
    // This is your basic binary search tree.
    public class BinarySearchTree : IBagContainer<Tree, Node>.IBag
    {
        // We can search for objects we've put in the tree.
        public Node Search(object o) { return null; }
        // See what I did here? Insert doesn't return a Pointer or IPointer,
        // it returns a Node! Covariant return types!
        public override Node Insert(object o) { return null; }
    }
    // A node in the binary tree. This is a basic example of an IPointer.
    public class BinarySearchTreeNode : IBagContainer<Tree, Node>.IPointer
    {
        // Moar covariant return types!
        public override Tree GetSet() { return null; }
        // If we maintain next and prev pointers in every node,
        // these operations are O(1). You can't expect every IBag
        // to support these operations.
        public Node GetNext() { return null; }
        public Node GetPrev() { return null; }
    }
}

瞧,我们已经实现了协变返回类型!然而,有一个小细节。

尝试实例化BinarySearchTree。为此,我们需要为一些合适的Tree和Node类指定BinarySearchTreeContainer.BinarySearchTree。对于Tree,我们希望使用BinarySearchTree,为此我们需要指定BinarySearchTreeContainer。

这本质上是一种奇怪的重复模板模式(CRTP)。不幸的是,我们无法像在CRTP:中那样修复它

public class BinarySearchTreeContainer
    : BinarySearchTreeContainer
        <BinarySearchTreeContainer.BinarySearchTree,
         BinarySearchTreeContainer.BinarySearchTreeNode> { }
public class IBagContainer
    : IBagContainer
        <IBagContainer.IBag,
         IBagContainer.IPointer> { }
(...)
BinarySearchTreeContainer.BinarySearchTree tree
    = new BinarySearchTreeContainer.BinarySearchTree();
tree.Search(null);
IBagContainer.IBag bag = tree; // No cast!
//bag.Search(null); // Invalid!
//BinarySearchTreeContainer.BinarySearchTreeNode node
//    = bag.Insert(null); // Invalid!

我们回到我最初的问题:前两个类定义是C#规范不允许的。如果允许这个类定义,我的二进制搜索树将是可用的。现在,它们只是编译:不能使用。

为什么可以';t基类规范的含义递归地依赖于C#中的基类规范本身

在过去的几年里,我与你提出的问题斗争了无数个小时。对你提出的所有问题的详细讨论需要我几个小时才能打字,所以我只总结一下:

首先,事实证明,即使Mads和我添加了"暂时假设为对象"条款,试图收紧规范的这一部分,规范的这部分仍然没有充分的依据。规范中的"如何将名称绑定到类型"部分假设所有嵌套和继承关系在查找时都是已知的且一致的,但显然情况并非如此,因为我们首先进行名称查找的全部原因是确定基类型。如果我有笔记的话,我可以给你举几个疯狂类型层次结构的例子,在这些例子中,泛型、嵌套、接口和基类的组合将编译器置于这样的情况下,即如何确定给定名称的含义取决于你计算基类的顺序。

显然,这不是一个好地方。我们不希望在重新排序文件中的类时,C#程序的意义有所不同!

其次,我们受到元数据中可以表示的内容的限制。

第三,从历史上看,我们一直受到可以有效地将发射到元数据中的内容的限制。如果尝试在基类型之前发射派生类型或在外部类型之前发射内部类型,则元数据发射器的早期版本会出现性能或正确性问题。(我在C#4中试图通过编写一个拓扑分类器来解决这个问题,该分类器可以找到一个有效的排序(如果存在的话),但事实证明,这个变化非常复杂和危险,我们决定在Roslyn之前不进行更改。在Roslynn中,我们使用了一个完全不同的发射器。)

第四,这类拓扑很少出现在实际的生产代码中;你显然是个例外。

第五,我们对语言的主要目标之一是使其成为一种"高质量的语言",在这种语言中,语言的特点使人们能够编写正确且可理解的程序。允许你在C++模板中看到的那种疯狂的"奇怪的重复出现"模式显然不是C#语言团队的目标。我们对提供一个理论上完整的类型系统不感兴趣;我们感兴趣的是让员工更容易地表现为一种人。

所有这些因素都不利于使基类和嵌套类关系中的循环更加合法。尽管我个人很喜欢提出一个有充分基础的系统来解决基类型中的循环,以不破坏任何现有代码,但这并不是一个足够高的优先级;我们有一长串想要为Roslyn改进的东西,而基类解析算法远不是最重要的。