Ceci est Sparta, ou est-ce?

Ce qui suit est une question d’entretien. Je suis venu avec une solution, mais je ne sais pas pourquoi cela fonctionne.


Question:

Sans modifier la classe Sparta , écrivez du code qui rend MakeItReturnFalse return false .

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

Ma solution: (SPOILER)

public class Place
{
public interface Sparta { }
}

Mais pourquoi Sparta dans MakeItReturnFalse() référence à {namespace}.Place.Sparta au lieu de {namespace}.Sparta ?

Mais pourquoi Sparta dans MakeItReturnFalse() référence à {namespace}.Place.Sparta au lieu 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’espace de noms et de noms”).

Le premier couple de balles – tronquées et annotées – se lit comme suit:

  • Si le namespace-or-type-name est de la forme I ou de la forme I [so K = 0 dans notre cas] :
    • Si K est égal à zéro et que le nom de l’espace de nom ou de type apparaît dans une déclaration de méthode générique [nope, pas de méthode générique]
    • Sinon, si le nom de l’espace de nom ou de type apparaît dans une déclaration de type, 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 le type d’instance de chaque classe déclaration de structure (le cas échéant):
      • Si K est égal à zéro et que la déclaration de T inclut un paramètre de type avec le nom I , alors le nom-espace-de-nom-type fait référence à ce paramètre de type. [Nan]
      • Sinon, si le nom-espace-ou-type-nom apparaît dans le corps de la déclaration de type et que T ou l’un de ses types de base contiennent un type accessible nested avec des parameters de type I et K , alors fait référence à ce type construit avec les arguments de type donnés. [Bingo!]
  • Si les étapes précédentes ont échoué, alors, pour chaque espace de noms N , en commençant par l’espace de noms dans lequel se trouve le namespace ou le nom de type, en continuant chaque éventuel espace de noms et en finissant par le namespace global, évalué jusqu’à ce qu’une entité soit localisée:
    • Si K est égal à zéro et que I est le nom d’un espace de noms dans N , alors … [Oui, cela réussirait]

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

Notez que si vous faites du type nested Place.Sparta une classe plutôt qu’une interface, il comstack et renvoie false , mais le compilateur émet un avertissement car il sait qu’une instance de Sparta ne sera jamais une instance de la classe Place.Sparta . De même, si vous conservez une interface Place.Sparta mais que la classe Sparta sealed , vous recevrez un avertissement car aucune instance de Sparta ne pourra jamais implémenter l’interface.

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” est celle qui est choisie.

L’interface Sparta est définie dans une classe de base. La classe Sparta est définie dans l’espace de noms contenant. Les objects définis dans une classe de base sont plus proches que ceux définis dans le même espace de noms.

Belle question! J’aimerais append 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é des manières suivantes:

  • Imprimons les noms de types au lieu de les comparer à ceux de l’expression d’origine (c.-à-d., return this is Sparta ).
  • Définissons l’interface Athena dans la super-classe Place pour illustrer la résolution des noms d’interface.
  • Imprimons également le nom de type de this car il est lié dans la classe Sparta , 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 object Sparta et appelons les trois méthodes.

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

Le résultat obtenu est le suivant:

 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 ce Sparta – l’interface – qui sera d’abord disponible pour le mécanisme de recherche de nom et la sortie de notre code changera pour:

 Sparta Place+Sparta Place+Athena 

Nous avons donc MakeItReturnFalse la comparaison de types dans la définition de la fonction MakeItReturnFalse en définissant simplement l’interface Sparta dans la superclasse, qui se trouve d’abord dans la résolution de noms.

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