Ce qui suit est une question d'entrevue. Je suis venu avec une solution, mais je ne sais pas pourquoi cela fonctionne.
Question:
Sans modifier la classe Sparta
, écrivez du code permettant à MakeItReturnFalse
de retourner 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()
fait-il référence à {namespace}.Place.Sparta
Au lieu de {namespace}.Sparta
?
Mais pourquoi
Sparta
dansMakeItReturnFalse()
fait-il 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 nom. Dans la spécification C # 5, les règles de dénomination pertinentes sont décrites à la section 3.8 ("Noms des espaces de noms et des types").
Le premier couple de balles - tronqué et annoté - se lit comme suit:
- Si le nom de l'espace de nom ou du type est de la forme
I
ou de la formeI<A1, ..., AK>
[donc K = 0 dans notre cas]:
- Si K est zéro et que le nom-type-ou-nom-type apparaît dans une déclaration de méthode générique [nope, pas de méthodes génériques]
- Sinon, si le nom d'espace-de-noms 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 avec le type d'instance de chaque classe englobante ou déclaration de structure (le cas échéant):
- Si
K
vaut zéro et que la déclaration deT
inclut un paramètre de type portant le nomI
, le nom d'espace de nom ou de type fait référence à ce paramètre de type. [Nope]- Sinon, si nom-type-ou-nom-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 le nomI
etK
paramètres de type, puis le espace de nom ou type-nom fait référence à ce 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 le nom de type est indiqué, en continuant avec chaque espace de noms (le cas échéant) et en se terminant par l’espace de noms global. , les étapes suivantes sont évaluées jusqu’à la localisation d’une entité:
- Si
K
vaut zéro etI
est le nom d’un espace de noms dansN
, alors ... [Oui, ça réussirait à réussir]
Donc, ce dernier point est ce qui ramène 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 on considère la classe Sparta
.
Notez que si vous faites le type imbriqué Place.Sparta
une classe plutôt qu’une interface, il compile toujours et retourne 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 gardez Place.Sparta
une interface mais en faisant Sparta
classe 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 qui soit "la plus proche", c'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 choses définies dans une classe de base sont "plus proches" que les choses définies dans le même espace de noms.
Belle question! J'aimerais ajouter une explication un peu plus longue pour ceux qui ne font pas le 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 original, légèrement modifié des manières suivantes:
return this is Sparta
).Athena
dans la superclasse Place
pour illustrer la résolution du nom d'interface.this
tel qu'il est lié à 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 objet Sparta
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 obtenu 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 ce Sparta
- l’interface - qui sera disponible en premier pour le mécanisme de recherche de nom et la sortie de notre code sera modifiée en:
Sparta
Place+Sparta
Place+Athena
Nous avons donc effectivement bousillé la comparaison des types dans la définition de la fonction MakeItReturnFalse
en définissant simplement l’interface Sparta dans la superclasse, qui se trouve d’abord par la résolution du nom.
Mais pourquoi C # a-t-il choisi de hiérarchiser 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 #.