Das ist Sparta, oder?

121

Das Folgende ist eine Interviewfrage. Ich habe eine Lösung gefunden, bin mir aber nicht sicher, warum sie funktioniert.


Frage:

SpartaSchreiben Sie einen Code, der die MakeItReturnFalseRückgabe bewirkt, ohne die Klasse zu ändern false.

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

Meine Lösung: (SPOILER)

public class Place
{
public interface Sparta { }
}

Aber warum bezieht sich Spartain MakeItReturnFalse()auf {namespace}.Place.Spartastatt {namespace}.Sparta?

budi
quelle
5
Könnten Sie bitte Spoiler Alert oder etwas zur Lösung hinzufügen? Ich bin so enttäuscht, dass ich keine Chance bekomme, es alleine zu lösen. Die Frage ist in der Tat erstaunlich.
Karolis Kajenas
1
Ich habe anfangs Spoiler-Tags eingefügt, diese wurden jedoch während der gesamten Lebensdauer dieses Beitrags mehrmals von der Community bearbeitet. Das tut mir leid.
Budi
1
Ich mag den Titel dieses Beitrags nicht. es ist besser für codegolf.SE geeignet. Können wir es in etwas ändern, das die Frage tatsächlich beschreibt?
Supuhstar
3
Süßes Puzzle. Schreckliche Interviewfrage, aber süßes Puzzle. Jetzt, da Sie wissen, wie und warum dies funktioniert, sollten Sie diese härtere Version ausprobieren
Eric Lippert

Antworten:

117

Aber warum bezieht sich Spartain MakeItReturnFalse()auf {namespace}.Place.Spartastatt {namespace}.Sparta?

Grundsätzlich, denn das sagen die Namenssuchregeln. In der C # 5-Spezifikation finden Sie die relevanten Namensregeln in Abschnitt 3.8 ("Namespace- und Typnamen").

Die ersten paar Kugeln - abgeschnitten und kommentiert - lauteten:

  • Wenn der Namespace- oder Typname die Form Ioder die Form hat I<A1, ..., AK> [also in unserem Fall K = 0] :
    • Wenn K Null ist und der Namespace- oder Typname in einer generischen Methodendeklaration erscheint [nein, keine generischen Methoden]
    • Andernfalls, wenn der Namespace- oder Typname in einer Typdeklaration erscheint, beginnt für jeden Instanztyp T (§10.3.1) mit dem Instanztyp dieser Typdeklaration und fährt mit dem Instanztyp jeder einschließenden Klasse oder fort Strukturdeklaration (falls vorhanden):
      • Wenn KNull ist und die Deklaration von Teinen Typparameter mit Name enthält I, bezieht sich der Namespace- oder Typname auf diesen Typparameter. [Nee]
      • Andernfalls, wenn der Namespace- oder Typname im Hauptteil der Typdeklaration angezeigt wird und T einer seiner Basistypen einen verschachtelten zugänglichen Typ mit dem Namen Iund enthältK Typparametern enthält, bezieht sich der Namespace- oder Typname darauf Typ, der mit den angegebenen Typargumenten erstellt wurde. [Bingo!]
  • Wenn die vorherigen Schritte nicht erfolgreich waren, werden für jeden Namespace Ndie folgenden Schritte ausgewertet, beginnend mit dem Namespace, in dem der Namespace- oder Typname vorkommt, mit jedem einschließenden Namespace (falls vorhanden) fortfahren und mit dem globalen Namespace enden bis eine Entität gefunden wird:
    • Wenn KNull ist und Ider Name eines Namespace in ist N, dann ... [Ja, das wäre erfolgreich]

Dieser letzte Punkt ist es also, der die Klasse aufnimmtSparta wenn der erste Aufzählungspunkt nichts findet ... aber wenn die Basisklasse Placeeine Schnittstelle definiert Sparta, wird sie gefunden, bevor wir die SpartaKlasse betrachten.

Beachten Sie, dass der verschachtelte Typ, wenn Sie ihn zu Place.Spartaeiner Klasse und nicht zu einer Schnittstelle machen, weiterhin kompiliert und zurückgegeben wird. falseDer Compiler gibt jedoch eine Warnung aus, da er weiß, dass eine Instanz von Spartaniemals eine Instanz der Klasse sein wird Place.Sparta. Wenn Sie Place.Spartaeine Schnittstelle behalten , aber die SpartaKlasse erstellen sealed, erhalten Sie ebenfalls eine Warnung, da keine SpartaInstanz die Schnittstelle jemals implementieren könnte.

Jon Skeet
quelle
2
Eine weitere zufällige Beobachtung: Unter Verwendung der ursprünglichen SpartaKlasse wird this is Placezurückgegeben true. Das Hinzufügen public interface Place { }zur SpartaKlasse führt jedoch this is Placezur Rückkehr false. Lässt meinen Kopf drehen.
Budi
@budi: Richtig, denn wieder findet die frühere Kugel Placeals Schnittstelle.
Jon Skeet
22

Beim Auflösen eines Namens auf seinen Wert wird die "Nähe" der Definition verwendet, um Mehrdeutigkeiten aufzulösen. Welche Definition auch immer "am nächsten" ist, es wird gewählt.

Die Schnittstelle Spartawird innerhalb einer Basisklasse definiert. Die Klasse Spartawird im enthaltenen Namespace definiert. Innerhalb einer Basisklasse definierte Dinge sind "näher" als Dinge, die im selben Namespace definiert sind.

Servieren
quelle
1
Und stellen Sie sich vor, die Namenssuche hätte nicht so funktioniert. Dann würde Arbeitscode, der eine innere Klasse enthält, beschädigt, wenn jemand zufällig eine Klasse der obersten Ebene mit demselben Namen hinzufügt.
Dan04
1
@ dan04: Stattdessen wird Arbeitscode, der keine verschachtelte Klasse enthält, beschädigt, wenn jemand eine verschachtelte Klasse mit demselben Namen wie eine Klasse der obersten Ebene hinzufügt . Es ist also nicht gerade ein totales "Gewinn" -Szenario.
Jon Skeet
1
@ JonSkeet Ich würde sagen, das Hinzufügen einer solchen verschachtelten Klasse ist eine Änderung in einem Bereich, von dem der Arbeitscode vernünftige Gründe hat, betroffen zu sein, und achten Sie auf Änderungen. Das Hinzufügen einer völlig unabhängigen Top-Level-Klasse ist viel weiter entfernt.
Angew ist nicht mehr stolz auf SO
2
@ JonSkeet Ist das nicht nur das Problem der Brittle Base Class in einer etwas anderen Form?
João Mendes
1
@ JoãoMendes: Ja, so ziemlich.
Jon Skeet
1

Schöne Frage! Ich möchte eine etwas längere Erklärung für diejenigen hinzufügen, die nicht täglich C # machen ... weil die Frage eine gute Erinnerung an Probleme mit der Namensauflösung im Allgemeinen ist.

Nehmen Sie den Originalcode, der auf folgende Weise leicht modifiziert wurde:

  • Drucken wir die Typnamen aus, anstatt sie wie im ursprünglichen Ausdruck (dh return this is Sparta) zu vergleichen.
  • Definieren wir die Schnittstelle Athenain der PlaceOberklasse, um die Auflösung des Schnittstellennamens zu veranschaulichen.
  • Drucken wir auch den Typnamen aus, der thisin der SpartaKlasse gebunden ist , um alles sehr klar zu machen.

Der Code sieht folgendermaßen aus:

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));
    }
}

Wir erstellen nun ein SpartaObjekt und rufen die drei Methoden auf.

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

Die Ausgabe, die wir erhalten, ist:

Sparta
Athena
Place+Athena

Wenn wir jedoch die Place-Klasse ändern und die Schnittstelle Sparta definieren:

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

Dann ist es diese Sparta- die Schnittstelle -, die zuerst für den Namenssuchmechanismus verfügbar ist, und die Ausgabe unseres Codes ändert sich zu:

Sparta
Place+Sparta
Place+Athena

Wir haben also den Typvergleich in der MakeItReturnFalseFunktionsdefinition effektiv durcheinander gebracht, indem wir die Sparta-Schnittstelle in der Oberklasse definiert haben, die zuerst durch die Namensauflösung ermittelt wird.

Aber warum hat C # in der Namensauflösung in der Oberklasse definierte Schnittstellen priorisiert? @ JonSkeet weiß! Und wenn Sie seine Antwort lesen, erhalten Sie die Details des Namensauflösungsprotokolls in C #.

mircealungu
quelle