To jest Sparta, czy to jest?

121

Poniżej znajduje się pytanie do wywiadu. Wymyśliłem rozwiązanie, ale nie jestem pewien, dlaczego działa.


Pytanie:

Bez modyfikowania Spartaklasy napisz kod, który spowoduje MakeItReturnFalsezwrot false.

public class Sparta : Place
{
    public bool MakeItReturnFalse()
    {
        return this is Sparta;
    }
}

Moje rozwiązanie: (SPOILER)

public class Place
{
public interface Sparta { }
}

Ale dlaczego Spartaw MakeItReturnFalse()odnosi się {namespace}.Place.Spartazamiast {namespace}.Sparta?

budi
źródło
5
Czy możesz dodać Spoiler Alert lub coś takiego do rozwiązania? Jestem tak zawiedziony, że nie mam szansy rozwiązać go samodzielnie. Pytanie jest rzeczywiście niesamowite.
Karolis Kajenas
1
Początkowo dodałem tagi spoilera, jednak były one edytowane przez społeczność wiele razy przez cały czas trwania tego posta. Przepraszam za to.
budi
1
Nie podoba mi się tytuł tego posta; jest bardziej odpowiedni dla codegolf.SE. Czy możemy zmienić to na coś, co faktycznie opisuje pytanie?
Supuhstar
3
Śliczne puzzle. Okropne pytanie do wywiadu, ale urocza łamigłówka. Teraz, kiedy już wiesz, jak i dlaczego to działa, powinieneś
Eric Lippert

Odpowiedzi:

117

Ale dlaczego Spartaw MakeItReturnFalse()odnosi się {namespace}.Place.Spartazamiast {namespace}.Sparta?

Zasadniczo dlatego, że tak mówią reguły wyszukiwania nazw. W specyfikacji C # 5 odpowiednie reguły nazewnictwa znajdują się w sekcji 3.8 („Przestrzenie nazw i nazwy typów”).

Pierwsze kilka punktorów - skrócone i opatrzone adnotacjami - czytaj:

  • Jeśli nazwa przestrzeni lub typu ma postać Ilub formę I<A1, ..., AK> [czyli K = 0 w naszym przypadku] :
    • Jeśli K jest równe zero, a nazwa przestrzeni nazw lub typu pojawia się w deklaracji metody ogólnej [nie, brak metod ogólnych]
    • W przeciwnym razie, jeśli nazwa przestrzeni nazw lub typu pojawia się w deklaracji typu, to dla każdego typu wystąpienia typu T (§10.3.1), rozpoczynając od typu wystąpienia deklaracji tego typu i kontynuując typ wystąpienia każdej otaczającej klasy lub deklaracja struktury (jeśli istnieje):
      • Jeśli Kwynosi zero, a deklaracja Tzawiera parametr typu o nazwie I, wówczas nazwa przestrzeni nazw lub typu odwołuje się do tego parametru typu. [Nie]
      • W przeciwnym razie, jeśli nazwa przestrzeni nazw lub typu pojawia się w treści deklaracji typu i T lub którykolwiek z jej typów podstawowych zawiera zagnieżdżony dostępny typ o parametrach nazwy Ii Ktypu, wówczas nazwa przestrzeni nazw lub typu odnosi się do tego typ skonstruowany z podanymi argumentami typu. [Bingo!]
  • Jeśli poprzednie kroki zakończyły się niepowodzeniem, dla każdej przestrzeni nazw N, zaczynając od przestrzeni nazw, w której występuje nazwa przestrzeni nazw lub typu, kontynuując każdą otaczającą przestrzeń nazw (jeśli istnieje) i kończąc na globalnej przestrzeni nazw, oceniane są następujące kroki dopóki jednostka nie zostanie zlokalizowana:
    • Jeśli Kwynosi zero i Ijest nazwą przestrzeni nazw w N, to ... [Tak, to się powiedzie]

Tak więc ostatni punkt jest tym, co podnosi Sparta klasę, jeśli pierwszy punktor niczego nie znajdzie ... ale kiedy klasa bazowa Placedefiniuje interfejs Sparta, zostaje znaleziona, zanim rozważymy Spartaklasę.

Zwróć uwagę, że jeśli ustawisz typ zagnieżdżony Place.Spartajako klasę, a nie interfejs, nadal kompiluje się i zwraca false- ale kompilator wyświetla ostrzeżenie, ponieważ wie, że instancja klasy Spartanigdy nie będzie instancją tej klasy Place.Sparta. Podobnie, jeśli zachowasz Place.Spartainterfejs, ale utworzysz Spartaklasę sealed, otrzymasz ostrzeżenie, ponieważ żadna Spartainstancja nie może zaimplementować interfejsu.

Jon Skeet
źródło
2
Kolejna przypadkowa obserwacja: przy użyciu oryginalnej Spartaklasy this is Placezwraca true. Jednak dodanie public interface Place { }do Spartaklasy powoduje this is Placepowrót false. Kręci mi się w głowie.
budi
@budi: Zgadza się, ponieważ ponownie, wcześniejszy punktor znajduje się Placejako interfejs.
Jon Skeet
22

Przy tłumaczeniu nazwy na jej wartość, „bliskość” definicji służy do rozwiązywania niejednoznaczności. Każda definicja jest „najbliższa” jest tą, która jest wybrana.

Interfejs Spartajest zdefiniowany w klasie bazowej. Klasa Spartajest zdefiniowana w zawierającej przestrzeń nazw. Rzeczy zdefiniowane w klasie bazowej są „bliżej” niż rzeczy zdefiniowane w tej samej przestrzeni nazw.

Servy
źródło
1
Wyobraź sobie, że wyszukiwanie nazw nie działa w ten sposób. Następnie działający kod, który zawiera klasę wewnętrzną, zostałby uszkodzony, gdyby ktoś dodał klasę najwyższego poziomu o tej samej nazwie.
dan04
1
@ dan04: Zamiast tego działający kod, który nie zawiera zagnieżdżonej klasy, zostaje zepsuty, jeśli ktoś doda zagnieżdżoną klasę o tej samej nazwie, co klasa najwyższego poziomu. Więc nie jest to dokładnie scenariusz „wygranej”.
Jon Skeet
1
@JonSkeet Powiedziałbym, że dodanie takiej zagnieżdżonej klasy jest zmianą w obszarze, na który działający kod ma uzasadnione powody, aby mieć na niego wpływ, i uważaj na zmiany. Dodanie zupełnie niepowiązanej klasy najwyższego poziomu jest znacznie dalej usuwane.
Angew nie jest już dumny z SO
2
@JonSkeet Czy to nie tylko problem kruchej klasy bazowej w nieco innym wydaniu?
João Mendes
1
@ JoãoMendes: Tak, prawie.
Jon Skeet
1

Piękne pytanie! Chciałbym dodać nieco dłuższe wyjaśnienie dla tych, którzy na co dzień nie robią C # ... ponieważ to pytanie jest dobrym przypomnieniem ogólnych problemów z rozwiązywaniem nazw.

Weź oryginalny kod, nieznacznie zmodyfikowany w następujący sposób:

  • Wydrukujmy nazwy typów zamiast porównywać je jak w oryginalnym wyrażeniu (tj return this is Sparta.).
  • Zdefiniujmy interfejs Athenaw Placenadklasie, aby zilustrować rozwiązywanie nazw interfejsów.
  • Wydrukujmy również nazwę typu, thisponieważ jest ona związana w Spartaklasie, aby wszystko było bardzo jasne.

Kod wygląda następująco:

public class Place {
    public interface Athena { }
}

public class Sparta : Place
{
    public void printTypeOfThis()
    {
        Console.WriteLine (this.GetType().Name);
    }

    public void printTypeOfSparta()
    {
        Console.WriteLine (typeof(Sparta));
    }

    public void printTypeOfAthena()
    {
        Console.WriteLine (typeof(Athena));
    }
}

Teraz tworzymy Spartaobiekt i wywołujemy trzy metody.

public static void Main(string[] args)
    {
        Sparta s = new Sparta();
        s.printTypeOfThis();
        s.printTypeOfSparta();
        s.printTypeOfAthena();
    }
}

Wynik, jaki otrzymujemy, to:

Sparta
Athena
Place+Athena

Jeśli jednak zmodyfikujemy klasę Place i zdefiniujemy interfejs Sparta:

   public class Place {
        public interface Athena { }
        public interface Sparta { } 
    }

to właśnie ten Sparta- interfejs - będzie dostępny jako pierwszy dla mechanizmu wyszukiwania nazw, a wynik naszego kodu zmieni się na:

Sparta
Place+Sparta
Place+Athena

Tak więc skutecznie pomieszaliśmy porównanie typów w MakeItReturnFalsedefinicji funkcji, po prostu definiując interfejs Sparty w nadklasie, który znajduje się jako pierwszy na podstawie rozpoznawania nazw.

Ale dlaczego C # wybrał priorytetyzację interfejsów zdefiniowanych w nadklasie w rozpoznawaniu nazw? @JonSkeet wie! A jeśli przeczytasz jego odpowiedź, zobaczysz szczegóły protokołu rozpoznawania nazw w C #.

mircealungu
źródło