Phần sau của C# mã không biên dịch:Tại sao ý nghĩa của đặc tả lớp cơ sở không phụ thuộc vào chính nó trong 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.
{ }
Hành vi này là đúng theo đặc điểm kỹ thuật C# 4.0 (đoạn 10.1.4.1):
Trong khi xác định ý nghĩa của các cơ sở trực tiếp đặc tả lớp A của một lớp B, lớp cơ sở trực tiếp của B tạm thời được giả định là đối tượng. Trực giác điều này đảm bảo rằng ý nghĩa của một đặc tả lớp cơ sở không thể đệ quy phụ thuộc vào chính nó.
Câu hỏi của tôi là: tại sao hành vi này không được phép? Intellisense không có vấn đề với nó - mặc dù tôi biết điều đó không nói nhiều, sau khi chứng kiến Visual Studio sụp đổ khi Intellisense cố gắng làm cho tinh thần của một số kết hợp lớp ác với các biến thể generics.
Tìm kiếm báo giá ở trên từ thông số kỹ thuật không mang lại gì, vì vậy tôi đoán điều này chưa được nêu ra ở bất kỳ đâu.
Tại sao tôi quan tâm? Tôi đã thiết kế đoạn mã sau:
// 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; }
}
}
Lo behold, chúng tôi đã đạt được các loại trả về biến thể! Tuy nhiên, có một chi tiết nhỏ.
Thử khởi tạo BinarySearchTree. Để làm điều đó, chúng ta cần phải chỉ định BinarySearchTreeContainer.BinarySearchTree cho một số lớp Tree và Node phù hợp. Đối với cây, chúng tôi muốn sử dụng BinarySearchTree, mà chúng tôi sẽ cần phải xác định BinarySearchTreeContainer.BinarySearchTree ... Và chúng tôi đang mắc kẹt.
Đây thực chất là curiously recurring template pattern (CRTP). Rất tiếc, chúng tôi không thể khắc phục sự cố như trong 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!
Và chúng tôi quay lại câu hỏi ban đầu: hai định nghĩa lớp hàng đầu không được đặc tả C# cho phép. Nếu định nghĩa lớp này được cho phép, cây tìm kiếm nhị phân của tôi sẽ có thể sử dụng được. Ngay bây giờ, họ chỉ đơn thuần là biên dịch: họ không thể được sử dụng.
+1 Thú vị như CRTP là ... kiểu hệ thống ngớ ngẩn :) Tuy nhiên, câu hỏi thú vị. Bạn không chắc nó sẽ công bằng như thế nào với câu hỏi mang tính chủ quan trong bài viết, mặc dù có vẻ như có một số câu trả lời rất khách quan. –
Điều này thực sự hữu ích nếu các lớp lồng nhau cần truy cập vào các lớp bên ngoài của chúng ... Miễn là đây không phải là trường hợp bạn có thể khai báo các lớp lồng nhau bên ngoài các lớp chứa của chúng và bạn đã hoàn thành. – Nuffin