Почему значение спецификации базового класса не может рекурсивно зависеть от самого себя в C #?

Следующая часть кода 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):

При определении значения прямой спецификации базового класса A для класса B, прямой базовый класс 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
    where Bag : IBagContainer.IBag
    where Pointer : IBagContainer.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 : IBagContainer
    where Tree : BinarySearchTreeContainer.BinarySearchTree
    where Node : BinarySearchTreeContainer.BinarySearchTreeNode
{
    // This is your basic binary search tree.
    public class BinarySearchTree : IBagContainer.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.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. Для этого нам нужно указать BinarySearchTreeContainer.BinarySearchTree для некоторых подходящих классов Tree и Node. Для Tree мы хотели бы использовать BinarySearchTree, для которого нам нужно было бы указать BinarySearchTreeContainer.BinarySearchTree ... И мы застряли.

По сути, это любопытно повторяющийся шаблонный шаблон (CRTP). К сожалению, мы не можем исправить это, как в CRTP:

public class BinarySearchTreeContainer
    : BinarySearchTreeContainer
         { }
public class IBagContainer
    : IBagContainer
         { }

(...)
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 #. Если бы это определение класса было разрешено, мои бинарные деревья поиска можно было бы использовать. Прямо сейчас они просто компилируются: их нельзя использовать.

17
задан Alex ten Brink 7 January 2012 в 00:47
поделиться