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
dansMakeItReturnFalse()
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 formeI
[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 deT
inclut un paramètre de type avec le nomI
, 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 typeI
etK
, 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 queI
est le nom d’un espace de noms dansN
, 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:
return this is Sparta
). Athena
dans la super-classe Place
pour illustrer la résolution des noms d’interface. 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 #.