2012-01-07 27 views
17

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

+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. –

+0

Đ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

Trả lời

21

Tôi đã vật lộn với các vấn đề bạn mang đến vô số giờ trong vài năm qua. Một cuộc thảo luận chi tiết về tất cả các vấn đề bạn nêu ra sẽ đưa tôi vài giờ để đánh máy, vì vậy tôi sẽ chỉ tóm tắt:

Trước tiên, nó chỉ ra rằng ngay cả với mệnh đề "tạm thời giả định là đối tượng" mà Mads và I thêm vào thử và thắt chặt phần đó của spec, phần này của spec vẫn chưa được thành lập. Các "làm thế nào để ràng buộc một tên đến một loại" bit của đặc tả giả định rằng tất cả các mối quan hệ làm tổ và thừa kế được biết và nhất quán tại thời điểm tra cứu xảy ra, nhưng tất nhiên rõ ràng là không thể là trường hợp vì toàn bộ lý do chúng tôi thực hiện tra cứu tên ở nơi đầu tiên là xác định loại cơ sở. Nếu tôi có các ghi chú của tôi với tôi, tôi có thể cung cấp cho bạn một số ví dụ về các hệ thống cấp bậc điên, nơi kết hợp các generics, nestings, interfaces và các lớp cơ sở đưa trình biên dịch vào các tình huống mà bạn xác định ý nghĩa của tên. bạn đang tìm ra các lớp cơ sở.

Rõ ràng đó không phải là một nơi tốt. Chúng tôi không muốn ý nghĩa của một chương trình C# khác nhau khi bạn sắp xếp lại các lớp trong một tập tin!

Thứ hai, chúng tôi bị ràng buộc bởi những gì có thể được thể hiện trong siêu dữ liệu.

Thứ ba, về mặt lịch sử, chúng tôi đã bị hạn chế bởi những gì có thể là phát ra hiệu quả thành siêu dữ liệu. Các phiên bản trước của bộ phát siêu dữ liệu có các vấn đề hiệu suất hoặc chính xác nếu bạn cố gắng phát ra các kiểu có nguồn gốc trước các kiểu cơ sở hoặc kiểu bên trong trước các kiểu bên ngoài. (Tôi đã thử C# 4 để giải quyết vấn đề này bằng cách viết một máy phân loại topo để tìm một thứ tự hiệu quả nếu tồn tại, nhưng sự thay đổi được chứng minh là phức tạp và nguy hiểm đến mức chúng tôi quyết định không thay đổi cho đến Roslyn. một bộ phát hoàn toàn khác.)

Thứ tư, hiếm khi các loại cấu trúc liên kết này xuất hiện trong mã sản xuất thực; bạn có vẻ là một ngoại lệ cho quy tắc đó.

Thứ năm, một trong những mục tiêu chính của chúng tôi cho ngôn ngữ là làm cho nó thành ngôn ngữ "chất lượng", nơi các tính năng của ngôn ngữ dẫn đầu để viết các chương trình vừa chính xác vừa dễ hiểu. Cho phép các kiểu điên rồ "tò mò định kỳ" mà bạn thấy trong các mẫu C++ rõ ràng không phải là mục tiêu của nhóm ngôn ngữ C#. Chúng tôi không quan tâm đến việc cung cấp một hệ thống kiểu lý thuyết hoàn chỉnh; chúng tôi quan tâm đến việc làm cho nó dễ dàng để đại diện cho một Nhân viên là một loại Người.

Tất cả các yếu tố này đang làm việc chống lại việc làm tròn trong lớp cơ sở và các mối quan hệ lớp lồng nhau hợp pháp hơn. Cá nhân tôi sẽ thích thử thách với việc đưa ra một hệ thống tốt để giải quyết các vòng tròn trong các loại cơ sở theo cách không phá vỡ bất kỳ mã hiện có nào, nó không phải là một ưu tiên đủ cao; chúng tôi có một danh sách dài những thứ chúng tôi muốn cải thiện cho Roslyn và thuật toán phân giải lớp cơ sở nằm xa đầu danh sách đó.

+3

Đủ công bằng. Tôi sợ rằng đây là một vấn đề loại 'có thể sâu' - tôi đoán tôi sẽ phải nghĩ ra một thứ khác. Tôi rất muốn xem một số ví dụ bạn đề cập nếu bạn muốn có thời gian để thêm chúng vào! –

Các vấn đề liên quan