C'est Sparte, ou est-ce?

121

Ce qui suit est une question d'entrevue. J'ai trouvé une solution, mais je ne sais pas pourquoi cela fonctionne.


Question:

Sans modifier la Spartaclasse, écrivez du code qui rend le MakeItReturnFalseretour false.

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

Ma solution: (SPOILER)

public class Place
{
public interface Sparta { }
}

Mais pourquoi Spartaen MakeItReturnFalse()référence à la {namespace}.Place.Spartaplace de {namespace}.Sparta?

Budi
la source
5
Pourriez-vous s'il vous plaît ajouter Spoiler Alert ou quelque chose à Solution, s'il vous plaît? Je suis tellement déçu que je n'ai pas la chance de le résoudre moi-même. La question est en effet incroyable.
Karolis Kajenas
1
J'ai initialement inclus des balises spoiler, mais elles ont été modifiées par la communauté plusieurs fois au cours de la durée de vie de ce message. Désolé pour ça.
budi
1
Je n'aime pas le titre de ce message; c'est plus adapté à codegolf.SE. Pouvons-nous le changer en quelque chose qui décrit réellement la question?
Supuhstar
3
Puzzle mignon. Question d'entretien terrible, mais puzzle mignon. Maintenant que vous savez comment et pourquoi cela fonctionne, vous devriez
Eric Lippert

Réponses:

117

Mais pourquoi Spartaen MakeItReturnFalse()référence à la {namespace}.Place.Spartaplace de {namespace}.Sparta?

Fondamentalement, parce que c'est ce que disent les règles de recherche de noms. Dans la spécification C # 5, les règles de dénomination pertinentes se trouvent dans la section 3.8 («Noms d'espaces de noms et de types»).

Les deux premières puces - tronquées et annotées - se lisent:

  • Si le nom de l'espace ou du type est de la forme Iou de la forme I<A1, ..., AK> [donc K = 0 dans notre cas] :
    • Si K est égal à zéro et que le nom de l’espace de noms ou du type apparaît dans une déclaration de méthode générique [non, pas de méthodes génériques]
    • Sinon, si le nom de l'espace de nom ou du type apparaît dans une déclaration de type, alors pour chaque type d'instance T (§10.3.1), en commençant par le type d'instance de cette déclaration de type et en continuant avec le type d'instance de chaque classe englobante ou déclaration struct (le cas échéant):
      • Si Kest égal à zéro et que la déclaration de Tinclut un paramètre de type avec nom I, alors l'espace de noms-ou-type-nom fait référence à ce paramètre de type. [Nan]
      • Sinon, si le nom de l'espace de nom ou du type apparaît dans le corps de la déclaration de type, et / T ou l'un de ses types de base contient un type accessible imbriqué ayant des paramètres de nom Iet de Ktype, alors le nom d'espace ou de type fait référence à cela type construit avec les arguments de type donnés. [Bingo!]
  • Si les étapes précédentes ont échoué, pour chaque espace de noms N, en commençant par l'espace de noms dans lequel l'espace de noms ou de type-nom apparaît, en continuant avec chaque espace de noms englobant (le cas échéant) et en terminant par l'espace de noms global, les étapes suivantes sont évaluées jusqu'à ce qu'une entité soit localisée:
    • Si Kest égal à zéro et Iest le nom d'un espace de noms dans N, puis ... [Oui, ce serait réussir]

Donc, ce dernier point est ce qui ramasse la Sparta classe si la première puce ne trouve rien ... mais lorsque la classe de base Placedéfinit une interface Sparta, elle est trouvée avant que nous considérions la Spartaclasse.

Notez que si vous faites du type imbriqué Place.Spartaune classe plutôt qu'une interface, il compile et retourne toujours false- mais le compilateur émet un avertissement car il sait qu'une instance de Spartane sera jamais une instance de la classe Place.Sparta. De même, si vous conservez Place.Spartaune interface mais créez la Spartaclasse sealed, vous recevrez un avertissement car aucune Spartainstance ne pourra jamais implémenter l'interface.

Jon Skeet
la source
2
Une autre observation aléatoire: en utilisant la Spartaclasse d' origine , this is Placeretourne true. Cependant, l'ajout public interface Place { }à la Spartaclasse entraîne this is Placele retour false. Ça me fait tourner la tête.
budi
@budi: Exact, car encore une fois, la puce précédente se trouve Placecomme interface.
Jon Skeet
22

Lors de la résolution d'un nom à sa valeur, la "proximité" de la définition est utilisée pour résoudre les ambiguïtés. Quelle que soit la définition «la plus proche», c'est celle qui est choisie.

L'interface Spartaest définie dans une classe de base. La classe Spartaest définie dans l'espace de noms contenant. Les choses définies dans une classe de base sont "plus proches" que les choses définies dans le même espace de noms.

Servy
la source
1
Et imaginez si la recherche de nom ne fonctionnait pas de cette façon. Ensuite, le code de travail qui contient une classe interne serait cassé si quelqu'un ajoutait une classe de niveau supérieur avec le même nom.
dan04
1
@ dan04: Mais à la place, le code de travail qui ne contient pas de classe imbriquée est cassé si quelqu'un ajoute une classe imbriquée avec le même nom qu'une classe de niveau supérieur. Ce n'est donc pas exactement un scénario «gagnant» total.
Jon Skeet
1
@JonSkeet Je dirais que l'ajout d'une telle classe imbriquée est un changement dans une zone sur laquelle le code de travail a des raisons raisonnables d'être affecté, et surveillez les changements. L'ajout d'une classe de premier niveau totalement indépendante est beaucoup plus supprimé.
Angew n'est plus fier du SO
2
@JonSkeet N'est-ce pas seulement le problème de la classe de base fragile sous une forme légèrement différente, cependant?
João Mendes
1
@ JoãoMendes: Oui, à peu près.
Jon Skeet
1

Belle question! Je voudrais ajouter une explication un peu plus longue pour ceux qui ne font pas C # quotidiennement ... parce que la question est un bon rappel des problèmes de résolution de noms en général.

Prenez le code d'origine, légèrement modifié de la manière suivante:

  • Imprimons les noms des types au lieu de les comparer comme dans l'expression d'origine (ie return this is Sparta).
  • Définissons l'interface Athenadans lePlace superclasse pour illustrer la résolution des noms d'interface.
  • Imprimons également le nom du type de thistel qu'il est lié dans la Spartaclasse, juste pour que tout soit très clair.

Le code ressemble à ceci:

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

Nous créons maintenant un Spartaobjet et appelons les trois méthodes.

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

Le résultat que nous obtenons est:

Sparta
Athena
Place+Athena

Cependant, si nous modifions la classe Place et définissons l'interface Sparta:

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

alors c'est ceci Sparta- l'interface - qui sera disponible en premier pour le mécanisme de recherche de nom et la sortie de notre code changera en:

Sparta
Place+Sparta
Place+Athena

Nous avons donc effectivement foiré la comparaison de type dans la MakeItReturnFalsedéfinition de fonction en définissant simplement l'interface Sparta dans la superclasse, qui se trouve d'abord par la résolution de nom.

Mais pourquoi C # a-t-il choisi de prioriser les interfaces définies dans la superclasse dans la résolution de noms? @JonSkeet le sait! Et si vous lisez sa réponse, vous obtiendrez les détails du protocole de résolution de noms en C #.

mircealungu
la source