Следующий фрагмент кода С# не компилируется:
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.
{ }
Это поведение является правильным в соответствии со спецификацией С# 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<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. Для этого нам нужно указать BinarySearchTreeContainer.BinarySearchTree для некоторых подходящих классов Tree и Node. Для дерева мы хотели бы использовать 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!
И мы вернемся к моему первоначальному вопросу: определение двух верхних классов не допускается спецификацией С#. Если это определение класса было разрешено, мои двоичные деревья поиска могли бы использоваться. Прямо сейчас они просто компилируются: их нельзя использовать.