web-dev-qa-db-fra.com

Ceci est Sparta, ou est-ce?

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?

121
budi

Mais pourquoi Sparta dans MakeItReturnFalse() 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 forme I<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 de T inclut un paramètre de type portant le nom I, 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 Tou l'un de ses types de base contient un type accessible imbriqué ayant le nom I et K 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 et I est le nom d’un espace de noms dans N, 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.

118
Jon Skeet

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.

22
Servy

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:

  • Imprimons les noms de types au lieu de les comparer comme dans l'expression d'origine (c'est-à-dire return this is Sparta).
  • Définissons l'interface Athena dans la superclasse Place pour illustrer la résolution du nom d'interface.
  • Imprimons également le nom du type 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 #.

1
mircealungu