2012-01-07 9 views
17

Następujący fragment kodu C# nie opracowanie:Dlaczego znaczenie specyfikacji bazowej rekurencyjnie nie zależy od siebie w języku 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. 
{ } 

To zachowanie jest prawidłowy dla C# 4.0 Specification (ustęp 10.1.4.1)

Przy ustalaniu znaczenie bezpośredniego podstawy specyfikacja klasy A klasy B, bezpośrednia klasa bazowa B jest tymczasowo uznawana za obiekt. Intuicyjnie zapewnia to, że znaczenie specyfikacji klasy bazowej nie może rekurencyjnie od siebie zależeć.

Moje pytanie brzmi: dlaczego to zachowanie nie jest dozwolone?

Intellisense nie ma z tym problemu - chociaż wiem, że niewiele mówi, po wystąpieniu awarii Visual Studio, gdy Intellisense próbuje zrozumieć jakąś kombinację złej klasy z wariantami generycznymi.

Przeszukanie w Internecie powyższego cytatu ze specyfikacji nie daje żadnych rezultatów, więc domyślam się, że to nie zostało nigdzie dodane.

Dlaczego mnie to obchodzi? Zaprojektowałem następujący fragment kodu:

// 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 oto osiągnęliśmy kowariantna rodzajów zamian! Jest jednak jeden mały szczegół.

Spróbuj utworzyć instancję BinarySearchTree. Aby to zrobić, musimy określić BinarySearchTreeContainer.BinarySearchTree dla niektórych odpowiednich klas drzewa i węzła. W przypadku drzewa, chcielibyśmy użyć BinarySearchTree, dla którego musimy określić BinarySearchTreeContainer.BinarySearchTree ... I utknęliśmy. Jest to zasadniczo curiously recurring template pattern (CRTP). Niestety, nie możemy go naprawić jak w 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! 

I wracamy do mojego pierwotnego pytania: dwie górne definicje klasy nie są dozwolone przez specyfikację C#. Jeśli ta definicja klasy byłaby dozwolona, ​​moje binarne drzewa wyszukiwania byłyby użyteczne. Teraz tylko kompilują: nie można ich użyć.

+1

+1 Ciekawe, jak CRTP to ... głupie systemy typu :) Ciekawe pytanie jednak. Nie jestem pewny, jak będzie to uczciwe z subiektywnie brzmiącym pytaniem umieszczonym w poście, chociaż wydaje się, że istnieje wiele bardzo obiektywnych odpowiedzi. –

+0

Byłoby to użyteczne, gdyby zagnieżdżone klasy wymagały dostępu do internałów ich zewnętrznych klas ... Tak długo, jak nie jest to możliwe, można po prostu zadeklarować zagnieżdżone klasy poza ich klasami zawierającymi i gotowe. – Nuffin

Odpowiedz

21

Zmagałem się z problemami, które pojawiają się od niezliczonych godzin w ciągu ostatnich kilku lat. Szczegółowe omówienie wszystkich problemów, które podniosłeś, zajęłoby mi kilka godzin, aby napisać, więc podsumuję:

Po pierwsze, okazuje się, że nawet przy tym "tymczasowo przyjętym za obiekt" klauzuli, że Mads i ja dodano, aby spróbować i zaostrzyć tę sekcję specyfikacji, ta sekcja specyfikacji wciąż nie jest dobrze uzasadniona. "Jak powiązać nazwę z typem" bit specyfikacji zakłada, że ​​wszystkie relacje zagnieżdżania i dziedziczenia są znane i spójne w punkcie, w którym odbywa się wyszukiwanie, ale oczywiście oczywiście nie może tak być, ponieważ cały powód, dla którego jesteśmy wykonanie wyszukiwania nazw w pierwszej kolejności polega na określeniu typu podstawowego. Gdybym miał ze mną swoje notatki, mógłbym podać ci kilka przykładów zwariowanych hierarchii typów, w których kombinacje generycznych, zagnieżdżania, interfejsów i klas bazowych stawiają kompilator w sytuacjach, w których sposób ustalenie, co oznacza nazwa, zależy od kolejności, w jakiej wymyślasz klasy bazowe.

Oczywiście nie jest to dobre miejsce. Nie chcemy, aby znaczenie programu C# było inne, gdy zmieniasz kolejność klas w pliku!

Po drugie, jesteśmy ograniczeni tym, co może być reprezentowane w metadanych.

Po trzecie, historycznie byliśmy ograniczani przez to, co może być skutecznie emitowane do metadanych w postaci. Poprzednie wersje emiterów metadanych miały problemy z wydajnością lub poprawnością, jeśli próbowano emitować typy pochodne przed typami bazowymi lub typami wewnętrznymi przed typami zewnętrznymi. (Próbowałem w C# 4 rozwiązać ten problem, pisząc topologiczną sortownię, która mogłaby znaleźć efektywne zamówienie, gdyby istniało, ale zmiana okazała się wystarczająco skomplikowana i niebezpieczna, że ​​zdecydowaliśmy się nie przyjmować zmian do Roslyn. zupełnie inny emiter.)

Po czwarte, rzadko się zdarza, że ​​tego rodzaju topologie pojawiają się w prawdziwym kodzie produkcyjnym; jesteś najwyraźniej wyjątkiem od tej reguły.

Po piąte, jednym z naszych głównych celów dla języka jest uczynienie go językiem "dołu jakości", w którym cechy języka prowadzą do pisania programów, które są poprawne i zrozumiałe. Zezwalanie na różnego rodzaju szalone "ciekawie powtarzające się" wzorce widoczne w szablonach C++ wyraźnie nie jest celem zespołu języka C#. Nie jesteśmy zainteresowani zapewnieniem teoretycznie kompletnego systemu typów; chcemy ułatwić stwierdzenie, że Pracownik to rodzaj Osoby.

Wszystkie te czynniki działają przeciw przekierowaniu okręgów w klasie bazowej, a zagnieżdżone relacje klas są bardziej legalne. Tak samo jak ja osobiście cieszyłem się z wymyślenia dobrze ugruntowanego systemu rozwiązywania kolistych typów baz w sposób, który nie narusza żadnego istniejącego kodu, nie jest to wystarczająco wysoki priorytet; mamy długą listę rzeczy, które chcemy ulepszyć dla Roslyn, a algorytm rozdzielczości klasy podstawowej jest daleki od górnej części tej listy.

+3

Wystarczająco fair. Obawiałem się, że to kwestia "puszki z robakami" - myślę, że będę musiał wymyślić coś innego. Chciałbym zobaczyć niektóre przykłady, o których wspomniałeś, jeśli masz czas, aby je dodać! –

Powiązane problemy