Pourquoi le compilateur C # ne code pas d'erreur lorsqu'une méthode statique appelle une méthode d'instance?

110

Le code suivant a une méthode statique, Foo()appelant une méthode d'instance, Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

Il se compile sans erreur * mais génère une exception de liant d'exécution lors de l'exécution. La suppression du paramètre dynamique de ces méthodes provoque une erreur du compilateur, comme prévu.

Alors, pourquoi avoir un paramètre dynamique permet-il de compiler le code? ReSharper ne l'affiche pas non plus comme une erreur.

Edit 1: * dans Visual Studio 2008

Edit 2: ajouté sealedcar il est possible qu'une sous-classe puisse contenir une Bar(...)méthode statique . Même la version scellée se compile lorsqu'il n'est pas possible qu'une méthode autre que la méthode d'instance puisse être appelée au moment de l'exécution.

Mike Scott
la source
8
+1 pour une très bonne question
cuongle
40
C'est une question à Eric-Lippert.
Olivier Jacot-Descombes
3
je suis à peu près sûr que Jon Skeet saurait quoi faire avec ça aussi tho;) @ OlivierJacot-Descombes
Mille
2
@Olivier, Jon Skeet voulait probablement que le code compile, donc le compilateur le permet :-))
Mike Scott
5
Ceci est un autre exemple des raisons pour lesquelles vous ne devriez pas utiliser dynamicsauf si vous en avez vraiment besoin.
Servy

Réponses:

71

MISE À JOUR: La réponse ci-dessous a été écrite en 2012, avant l'introduction de C # 7.3 (mai 2018) . Dans Quoi de neuf dans C # 7.3 , la section Candidats de surcharge améliorés , élément 1, il est expliqué comment les règles de résolution de surcharge ont changé afin que les surcharges non statiques soient supprimées tôt. Donc, la réponse ci-dessous (et toute cette question) n'a pour la plupart qu'un intérêt historique maintenant!


(Pré C # 7.3 :)

Pour une raison quelconque, la résolution de surcharge trouve toujours la meilleure correspondance avant de vérifier les statiques par rapport aux non statiques. Veuillez essayer ce code avec tous les types statiques:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

Cela ne compilera pas car la meilleure surcharge est celle qui prend un string. Mais bon, c'est une méthode d'instance, donc le compilateur se plaint (au lieu de prendre la deuxième meilleure surcharge).

Ajout: Je pense donc que l'explication de l' dynamicexemple de la question originale est que, pour être cohérent, lorsque les types sont dynamiques, nous trouvons également d' abord la meilleure surcharge (ne vérifiant que le nombre de paramètres et les types de paramètres, etc., pas statique vs non -static), et ensuite seulement vérifier l'électricité statique. Mais cela signifie que la vérification statique doit attendre l'exécution. D'où le comportement observé.

Ajout tardif: Des informations sur les raisons pour lesquelles ils ont choisi de faire des choses dans cet ordre amusant peuvent être déduites de ce billet de blog d'Eric Lippert .

Jeppe Stig Nielsen
la source
Il n'y a pas de surcharge dans la question initiale. Les réponses montrant une surcharge statique ne sont pas pertinentes. Ce n'est pas valide de répondre "bien si vous avez écrit ceci ..." puisque je n'ai pas écrit cela :-)
Mike Scott
5
@MikeScott J'essaie juste de vous convaincre que la résolution de surcharge en C # va toujours comme ceci: (1) Trouvez la meilleure correspondance sans tenir compte de statique / non statique. (2) Nous savons maintenant quelle surcharge utiliser, puis vérifions l'électricité statique. Pour cette raison, quand a dynamicété introduit dans le langage, je pense que les concepteurs de C # ont dit: "Nous ne prendrons pas en considération (2) la compilation quand c'est une dynamicexpression." Mon but ici est donc de trouver une idée de la raison pour laquelle ils ont choisi de ne pas vérifier la statique par rapport à l'instance jusqu'à l'exécution. Je dirais que ce contrôle a lieu au moment de la liaison .
Jeppe Stig Nielsen
assez juste mais cela n'explique toujours pas pourquoi, dans ce cas, le compilateur ne peut pas résoudre l'appel à la méthode d'instance. En d'autres termes, la façon dont le compilateur effectue la résolution est simpliste - il ne reconnaît pas le cas simple comme mon exemple où il n'y a aucune possibilité qu'il ne puisse pas résoudre l'appel. L'ironie est la suivante: en ayant une seule méthode Bar () avec un paramètre dynamique, le compilateur ignore alors cette seule méthode Bar ().
Mike Scott du
45
J'ai écrit cette partie du compilateur C #, et Jeppe a raison. Veuillez voter pour cela. La résolution de surcharge se produit avant de vérifier si une méthode donnée est une méthode statique ou une méthode d'instance, et dans ce cas, nous reportons la résolution de surcharge à l'exécution et donc également la vérification statique / d'instance jusqu'à l'exécution. De plus, le compilateur fait de son mieux pour trouver statiquement les erreurs dynamiques, ce qui n'est absolument pas complet.
Chris Burrows
30

Foo a un paramètre "x" qui est dynamique, ce qui signifie que Bar (x) est une expression dynamique.

Il serait parfaitement possible pour Example d'avoir des méthodes comme:

static Bar(SomeType obj)

Dans ce cas, la méthode correcte serait résolue, donc l'instruction Bar (x) est parfaitement valide. Le fait qu'il existe une méthode d'instance Bar (x) est sans importance et même pas pris en compte: par définition , puisque Bar (x) est une expression dynamique, nous avons reporté la résolution à l'exécution.

Marc Gravell
la source
14
mais lorsque vous supprimez la méthode Instance Bar, elle ne se compile plus.
Justin Harvey du
1
@Justin intéressant - un avertissement? Ou une erreur? Dans tous les cas, il peut ne valider que dans la mesure où le groupe de méthodes, laissant la résolution de surcharge complète à l'exécution.
Marc Gravell
1
@Marc, puisqu'il n'y a pas d'autre méthode Bar (), vous ne répondez pas à la question. Pouvez-vous expliquer cela étant donné qu'il n'y a qu'une seule méthode Bar () sans surcharge? Pourquoi se reporter au runtime alors qu'il n'y a aucun moyen d'appeler une autre méthode? Ou y a-t-il? Remarque: j'ai modifié le code pour sceller la classe, qui se compile toujours.
Mike Scott du
1
@mike pour savoir pourquoi reporter au runtime: parce que c'est ce que signifie
Marc Gravell
2
@Mike impossible n'est pas le but; ce qui importe, c'est de savoir si c'est nécessaire . L'intérêt de la dynamique est que ce n'est pas le travail du compilateur.
Marc Gravell
9

L'expression "dynamique" sera liée pendant l'exécution, donc si vous définissez une méthode statique avec la signature correcte ou une méthode d'instance, le compilateur ne la vérifiera pas.

La «bonne» méthode sera déterminée pendant l'exécution. Le compilateur ne peut pas savoir s'il existe une méthode valide pendant l'exécution.

Le mot-clé "dynamic" est défini pour les langages dynamiques et de script, où la méthode peut être définie à tout moment, même pendant l'exécution. Des trucs fous

Voici un exemple qui gère les entiers mais pas de chaînes, car la méthode est sur l'instance.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Vous pouvez ajouter une méthode pour gérer tous les "faux" appels, qui n'ont pas pu être traités

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}
Oberfreak
la source
Le code d'appel de votre exemple ne devrait-il pas être Example.Bar (...) au lieu de Example.Foo (...)? Foo () n'est-il pas hors de propos dans votre exemple? Je ne comprends pas vraiment votre exemple. Pourquoi l'ajout de la méthode générique statique poserait-il un problème? Pourriez-vous modifier votre réponse pour inclure cette méthode au lieu de la donner en option?
Mike Scott du
mais l'exemple que j'ai posté n'a qu'une seule méthode d'instance et aucune surcharge, donc au moment de la compilation, vous savez qu'il n'y a pas de méthodes statiques possibles qui pourraient être résolues. Ce n'est que si vous en ajoutez au moins un que la situation change et que le code est valide.
Mike Scott du
Mais cet exemple a toujours plus d'une méthode Bar (). Mon exemple n'a qu'une seule méthode. Il n'y a donc aucune possibilité d'appeler une méthode statique Bar (). L'appel peut être résolu au moment de la compilation.
Mike Scott du
@Mike peut être! = Est; avec dynamique, il n'est pas nécessaire de le faire
Marc Gravell
@MarcGravell, veuillez clarifier?
Mike Scott du