为什么基类规范的含义在C#中递归依赖于它自身?

Ale*_*ink 17 c# generics inheritance

以下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.
{ }
Run Code Online (Sandbox Code Playgroud)

根据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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

瞧,我们已经实现了协变回报类型!但是有一个小细节.

尝试实例化BinarySearchTree.为此,我们需要为一些合适的Tree和Node类指定BinarySearchTreeContainer.BinarySearchTree.对于Tree,我们想使用BinarySearchTree,我们需要指定BinarySearchTreeContainer.BinarySearchTree ...而且我们被卡住了.

这基本上是奇怪的重复模板模式(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!
Run Code Online (Sandbox Code Playgroud)

我们回到原来的问题:C#规范不允许前两个类定义.如果允许这个类定义,我的二叉搜索树将是可用的.现在,他们只是编译:他们不能使用.

Eri*_*ert 21

在过去的几年里,我一直在努力解决你带来无数个小时的问题.你提出的所有问题的详细讨论将花费我几个小时来打字,所以我只是总结一下:

首先,事实证明,即使Mads和我添加的"暂时假定为对象"条款试图收紧规范的那一部分,规范的这一部分仍然没有充分根据.规范中的"如何将名称绑定到类型"位假定所有嵌套和继承关系在查找发生时都是已知且一致的,但当然显然不是这种情况,因为我们的全部原因都是首先进行名称查找是确定基本类型.如果我有我的笔记,我可以给你一些疯狂类型层次结构的例子,其中泛型,嵌套,接口和基类的组合将编译器置于如何确定给定名称的含义取决于其中的顺序的情况下你正在弄清楚基类.

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

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

第三,从历史上看,我们一直受限于能够有效地发射到元数据中的东西.如果您尝试在基类型之前发布派生类型或在外部类型之前发出内部类型,则以前版本的元数据发射器会出现性能或正确性问题.(我尝试在C#4中通过编写一个拓扑分类器来解决这个问题,如果有的话,它会找到一个有效的排序,但是这个变化被证明是非常复杂和危险的,我们决定在Roslyn之前不做更改.在Roslyn我们使用一个完全不同的发射器.)

第四,很少有这些类型的拓扑结构出现在实际生产代码中; 你显然是该规则的一个例外.

第五,我们对该语言的主要目标之一是使其成为一种"质量陷阱"的语言,其中语言的特征导致人们编写正确且易于理解的程序.允许您在C++模板中看到的各种疯狂的"奇怪的重复"模式显然不是C#语言团队的目标.我们对提供一个理论上完整的类型系统不感兴趣; 我们有兴趣轻松表示员工是一种人.

所有这些因素都在反对使基类中的循环和嵌套类关系更合法.尽管我个人会喜欢提出一个有充分理由的系统来解决基本类型中的循环,并且不会破坏任何现有代码,但它不是一个足够高的优先级.我们有很多我们想要为Roslyn改进的东西,并且基类解析算法远远不在该列表的顶部.

  • 很公平.我担心这是一个"可以蠕虫"类型的问题 - 我想我将不得不想出别的东西.如果您有时间添加它们,我很乐意看到您提到的一些示例! (3认同)