45 votes

La résolution de surcharge de la méthode C # ne sélectionne pas le remplacement concret générique

Ce programme C# complet illustre le problème:

public abstract class Executor<T>
{
    public abstract void Execute(T item);
}

class StringExecutor : Executor<string>
{
    public void Execute(object item)
    {
        // why does this method call back into itself instead of binding
        // to the more specific "string" overload.
        this.Execute((string)item);
    }

    public override void Execute(string item) { }
}

class Program
{
    static void Main(string[] args)
    {
        object item = "value";
        new StringExecutor()
            // stack overflow
            .Execute(item); 
    }
}

J'ai couru dans un StackOverlowException que je remonte à ce modèle d'appel où j'ai essayé de transférer des appels vers un plus spécifiques de surcharge. À ma grande surprise, l'invocation n'a pas la sélection de la plus spécifique à la surcharge cependant, mais de l'appeler en lui-même. Il a clairement quelque chose à voir avec le type de base étant générique, mais je ne comprends pas pourquoi il ne serait pas sélectionner l'Execute(string) surcharge.

Quelqu'un a une idée dans ce?

Le code ci-dessus a été simplifié pour voir le modèle, la structure réelle est un peu plus compliqué, mais le problème est le même.

31voto

Selman22 Points 44788

Ressemble à ce qui est mentionné dans la spécification C# 5.0, 7.5.3 Résolution de Surcharge:

Résolution de surcharge sélectionne la fonction de membre d'invoquer dans la suite des contextes distincts au sein de la C#:

  • L'Invocation d'une méthode nommée dans une invocation expression (§7.6.5.1).
  • L'Invocation d'une instance constructeur nommé dans un objet-création-expression (§7.6.10.1).
  • L'Invocation d'un indexeur accesseur par l'intermédiaire d'un élément d'accès (§7.6.6).
  • L'Invocation d'un modèle prédéfini ou défini par l'utilisateur de l'opérateur référencé dans une expression (§7.3.3 et §7.3.4).

Chacun de ces contextes définit l'ensemble des candidats à la fonction de membres et la liste des arguments en sa propre manière unique, comme décrit dans en détail dans les sections énumérées ci-dessus. Par exemple, le jeu de les candidats pour une invocation de méthode ne comporte pas de méthodes marqué remplacer (§7.4), et des méthodes dans une classe de base ne sont pas des candidats si tout méthode dans une classe dérivée est applicable (§7.6.5.1).

Quand on regarde 7.4:

Une recherche de membre d'un nom de N avec K paramètres de type de type T, est traité comme suit:

• Tout d'abord, un ensemble de membres accessibles nommé N est déterminé:

  • Si T est un paramètre de type, puis l'ensemble est l'union des ensembles de
    accessible membres nommés N dans chacun des types spécifiés comme une contrainte fondamentale ou secondaire contrainte (§10.1.5) pour T, avec l'ensemble des membres accessibles nommé N dans l'objet.

  • Sinon, l'ensemble se compose de tous accessibles (§3.5) membres nommés N en T, y compris les membres hérités et accessible membersnamed N dans l'objet. Si T est un type construit, l'ensemble des membres est obtenu en remplaçant les arguments de type comme décrit dans le §10.3.2. Les membres qui incluent un remplacement modificateur sont exclus de l'ensemble.

Si vous supprimez override le compilateur choisit Execute(string) de surcharge lorsque vous lancez l'élément.

24voto

David L Points 22922

Comme mentionné dans Jon Skeet de l' article sur la surcharge, lors de l'invocation d'une méthode dans une classe qui remplace également une méthode avec le même nom à partir d'une base de classe, le compilateur prendra toujours la méthode de la classe au lieu de le remplacer, quelle que soit la "specificness" de type, à condition que la signature est "compatible".

Jon va que c'est un excellent argument pour éviter de surcharger à travers l'héritage des limites, car c'est exactement le genre de comportement inattendu peut se produire.

17voto

Eric Lippert Points 300275

Comme d'autres réponses ont noté, c'est par la conception.

Considérons un moins compliquées exemple:

class Animal
{
  public virtual void Eat(Apple a) { ... }
}
class Giraffe : Animal
{
  public void Eat(Food f) { ... }
  public override void Eat(Apple a) { ... }
}

La question est pourquoi giraffe.Eat(apple) résout Giraffe.Eat(Food) et pas du virtuel, Animal.Eat(Apple).

C'est une conséquence de deux règles:

(1) Le type de récepteur est plus important que le type d'un argument lors de la résolution des surcharges.

J'espère que c'est clair pourquoi ce doit être le cas. La personne qui écrit la classe dérivée est strictement plus de connaissances que la personne qui écrit la classe de base, parce que la personne qui écrit la classe dérivée utilisé la classe de base, et non pas vice versa.

La personne qui a écrit Giraffe dit: "j'ai un moyen pour un Giraffe manger toute la nourriture", et qui nécessite des connaissances particulières de l'intérieur de la girafe de la digestion. Cette information n'est pas présente dans la classe de base de la mise en œuvre, qui ne sait que manger des pommes.

Afin de résolution de surcharge doit toujours privilégier le choix d'une méthode d'une classe dérivée sur le choix d'une méthode d'une classe de base, quel que soit le betterness de l'argument des conversions de type.

(2) le Choix de remplacer ou de ne pas surcharger une méthode virtuelle n'est pas une partie du public de la surface d'une classe. C'est un privé détail d'implémentation. Par conséquent, aucune décision ne doit être fait quand vous faites résolution de surcharge qui allait changer en fonction de si oui ou non une méthode est substituée.

Résolution de surcharge ne doit jamais dire "je vais choisir virtuel Animal.Eat(Apple) parce qu'il a été remplacé".

Maintenant, vous pourriez vous dire "OK, supposons que je suis à l'intérieur de la Girafe quand je fais l'appel." Code à l'intérieur de la Girafe possède toutes les connaissances de privé détails de mise en œuvre, à droite? Il peut donc prendre la décision de faire appel virtuel Animal.Eat(Apple) au lieu de Giraffe.Eat(Food) lorsqu'ils sont confrontés à giraffe.Eat(apple), droite? Parce qu'il sait qu'il y a une mise en œuvre qui comprend les besoins des girafes que manger des pommes.

C'est un remède pire que le mal. Maintenant, nous avons une situation où identiques code a différents comportements en fonction de l'endroit où elle de course! Vous pouvez imaginer avoir un appel à l' giraffe.Eat(apple) en dehors de la classe, refactoriser le code de sorte qu'il est à l'intérieur de la classe, et tout à coup observables changements de comportement!

Ou, pourrait-on dire, hey, je me rends compte que ma Girafe logique est en fait assez général pour passer à une classe de base, mais pas à l'Animal, donc je vais refactoriser mon Giraffe code pour:

class Mammal : Animal 
{
  public void Eat(Food f) { ... } 
  public override void Eat(Apple a) { ... }
}
class Giraffe : Mammal
{
  ...
}

Et maintenant, tous les appels d' giraffe.Eat(apple) à l'intérieur d' Giraffe coup à de différentes de résolution de surcharge comportement après le refactoring? Ce serait très inattendu!

C# est un pit-de-la réussite de la langue; nous voulons assurez-vous que simple refactorings comme la modification de l'emplacement dans une hiérarchie, une méthode est surchargée de ne pas provoquer des changements subtils dans le comportement.

En résumé:

  • Résolution de surcharge priorise les récepteurs sur les autres arguments, car l'appel de code spécialisé qui connaît le fonctionnement interne du récepteur est mieux que d'appeler plus générale de code qui ne fonctionne pas.
  • Si et lorsqu'une méthode est substituée n'est pas considéré comme lors de la résolution de surcharge; toutes les méthodes sont traitées comme si elles étaient jamais remplacé pour des fins de résolution de surcharge. C'est un détail d'implémentation qui ne font pas partie de la surface de la type.
  • La surcharge de résolution de problèmes sont résolus -- modulo l'accessibilité des cours! -- de la même façon, peu importe où le problème se produit dans le code. Nous n'avons pas un algorithme pour la résolution où le récepteur est du type de l'contenant le code, et un autre pour quand l'appel est dans une classe différente.

Réflexions supplémentaires sur des questions connexes peuvent être trouvés ici: https://ericlippert.com/2013/12/23/closer-is-better/ et ici https://blogs.msdn.microsoft.com/ericlippert/2007/09/04/future-breaking-changes-part-three/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X