Questa è Sparta, o no?


121

Quella che segue è una domanda dell'intervista. Ho trovato una soluzione, ma non sono sicuro del motivo per cui funziona.


Domanda:

Senza modificare la Spartaclasse, scrivi del codice che MakeItReturnFalseritorni false.

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

La mia soluzione: (SPOILER)

public class Place
{
public interface Sparta { }
}

Ma perché Spartain si MakeItReturnFalse()riferisce a {namespace}.Place.Spartainvece di {namespace}.Sparta?


5
Potresti aggiungere Spoiler Alert o qualcosa del genere a Solution, per favore? Sono così deluso che non ho la possibilità di risolverlo da solo. La domanda è davvero sorprendente.
Karolis Kajenas

1
Inizialmente ho incluso tag spoiler, tuttavia è stato modificato dalla community più volte nel corso di questo post. Mi dispiace per quello.
budi

1
Non mi piace il titolo di questo post; è più adatto per codegolf.SE. Possiamo cambiarlo in qualcosa che descriva effettivamente la domanda?
Supuhstar

3
Puzzle carino. Terribile domanda da intervista, ma simpatico puzzle. Ora che sai come e perché funziona, dovresti provare questa versione più difficile: stackoverflow.com/q/41319962/88656
Eric Lippert

Risposte:


117

Ma perché Spartain si MakeItReturnFalse()riferisce a {namespace}.Place.Spartainvece di {namespace}.Sparta?

Fondamentalmente, perché è quello che dicono le regole di ricerca del nome. Nella specifica C # 5, le regole di denominazione rilevanti sono nella sezione 3.8 ("Spazio dei nomi e nomi dei tipi").

La prima coppia di punti - troncati e annotati - leggi:

  • Se il nome dello spazio dei nomi o del tipo è della forma Io della forma I<A1, ..., AK> [quindi K = 0 nel nostro caso] :
    • Se K è zero e il nome dello spazio dei nomi o del tipo viene visualizzato all'interno di una dichiarazione di metodo generico [no, nessun metodo generico]
    • Altrimenti, se lo spazio dei nomi o il nome del tipo appare all'interno di una dichiarazione di tipo, allora per ogni istanza digitare T (§10.3.1), iniziando con il tipo di istanza di quella dichiarazione di tipo e continuando con il tipo di istanza di ciascuna classe che lo racchiude o dichiarazione struct (se presente):
      • Se Kè zero e la dichiarazione di Tinclude un parametro di tipo con nome I, lo spazio dei nomi o il nome del tipo si riferisce a quel parametro di tipo. [No]
      • Altrimenti, se lo spazio dei nomi o il nome del tipo appare nel corpo della dichiarazione del tipo e T o uno qualsiasi dei suoi tipi di base contiene un tipo accessibile annidato con parametri di nome Ie Ktipo, lo spazio dei nomi o il nome del tipo si riferisce a quello tipo costruito con gli argomenti di tipo forniti. [Bingo!]
  • Se i passaggi precedenti non hanno avuto esito positivo, per ogni spazio dei nomi N, a partire dallo spazio dei nomi in cui si trova lo spazio dei nomi o il nome del tipo, continuando con ogni spazio dei nomi racchiuso (se presente) e terminando con lo spazio dei nomi globale, vengono valutati i seguenti passaggi finché non si trova un'entità:
    • Se Kè zero ed Iè il nome di uno spazio dei nomi in N, allora ... [Sì, sarebbe riuscito]

Quindi il punto finale dell'elenco è ciò che rileva la Sparta classe se il primo punto non trova nulla ... ma quando la classe base Placedefinisce un'interfaccia Sparta, viene trovata prima di considerare la Spartaclasse.

Nota che se rendi il tipo annidato Place.Spartauna classe piuttosto che un'interfaccia, viene comunque compilato e restituito false, ma il compilatore emette un avviso perché sa che un'istanza di Spartanon sarà mai un'istanza della classe Place.Sparta. Allo stesso modo, se mantieni Place.Spartaun'interfaccia ma crei la Spartaclasse sealed, riceverai un avviso perché nessuna Spartaistanza potrebbe mai implementare l'interfaccia.


2
Un'altra osservazione casuale: utilizzando la Spartaclasse originale , this is Placerestituisce true. Tuttavia, l'aggiunta public interface Place { }alla Spartaclasse causa la this is Placerestituzione false. Mi fa girare la testa.
budi

@ budi: Giusto, perché ancora una volta, il punto precedente trova Placecome interfaccia.
Jon Skeet

22

Quando si risolve un nome nel suo valore, la "vicinanza" della definizione viene utilizzata per risolvere le ambiguità. Qualunque sia la definizione "più vicina" è quella scelta.

L'interfaccia Spartaè definita all'interno di una classe base. La classe Spartaè definita nello spazio dei nomi contenitore. Le cose definite all'interno di una classe base sono "più vicine" di quelle definite nello stesso spazio dei nomi.


1
E immagina se la ricerca del nome non funzionasse in questo modo. Quindi, il codice funzionante che contiene una classe interna verrebbe interrotto se qualcuno aggiungesse una classe di primo livello con lo stesso nome.
dan04

1
@ dan04: Ma invece, il codice funzionante che non contiene una classe nidificata viene interrotto se qualcuno aggiunge una classe nidificata con lo stesso nome di una classe di primo livello. Quindi non è esattamente uno scenario di "vittoria" totale.
Jon Skeet

1
@ JonSkeet Direi che l'aggiunta di una tale classe annidata è un cambiamento in un'area in cui il codice funzionante ha ragionevoli motivi per essere influenzato e controlla i cambiamenti. L'aggiunta di una classe di primo livello completamente non correlata è molto più rimossa.
Angew non è più orgoglioso di SO

2
@JonSkeet Non è solo il problema della Brittle Base Class in una veste leggermente diversa, però?
João Mendes

1
@ JoãoMendes: Sì, praticamente.
Jon Skeet

1

Bella domanda! Vorrei aggiungere una spiegazione leggermente più lunga per coloro che non fanno C # quotidianamente ... perché la domanda è un buon promemoria dei problemi di risoluzione dei nomi in generale.

Prendi il codice originale, leggermente modificato nei seguenti modi:

  • Stampiamo i nomi dei tipi invece di confrontarli come nell'espressione originale (es return this is Sparta ).
  • Definiamo l'interfaccia Athena nella Placesuperclasse per illustrare la risoluzione del nome dell'interfaccia.
  • Stampiamo anche il nome del tipo di thiscome è associato nella Spartaclasse, solo per rendere tutto molto chiaro.

Il codice ha questo aspetto:

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

Creiamo ora un Spartaoggetto e chiamiamo i tre metodi.

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

L'output che otteniamo è:

Sparta
Athena
Place+Athena

Tuttavia, se modifichiamo la classe Place e definiamo l'interfaccia Sparta:

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

quindi è questa Sparta- l'interfaccia - che sarà disponibile per prima per il meccanismo di ricerca del nome e l'output del nostro codice cambierà in:

Sparta
Place+Sparta
Place+Athena

Quindi abbiamo effettivamente incasinato il confronto dei tipi nella MakeItReturnFalsedefinizione della funzione semplicemente definendo l'interfaccia di Sparta nella superclasse, che si trova prima dalla risoluzione del nome.

Ma perché C # ha scelto di dare la priorità alle interfacce definite nella superclasse nella risoluzione dei nomi? @ JonSkeet lo sa! E se leggi la sua risposta otterrai i dettagli del protocollo di risoluzione dei nomi in C #.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.