2009-05-10 4 views
27

Je suis curieux de savoir pourquoi cela se passe. S'il vous plaît lire l'exemple de code ci-dessous et l'IL correspondant qui a été émis dans les commentaires ci-dessous chaque section:Pourquoi le compilateur C# émet-il une instruction callvirt pour un appel de méthode GetType()?

using System; 

class Program 
{ 
    static void Main() 
    { 
     Object o = new Object(); 
     o.GetType(); 

     // L_0001: newobj instance void [mscorlib]System.Object::.ctor() 
     // L_0006: stloc.0 
     // L_0007: ldloc.0 
     // L_0008: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() 

     new Object().GetType(); 

     // L_000e: newobj instance void [mscorlib]System.Object::.ctor() 
     // L_0013: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() 
    } 
} 

Pourquoi le compilateur émet un callvirt pour la première section, mais un call pour la deuxième section? Y a-t-il une raison quelconque pour que le compilateur émette une instruction callvirt pour une méthode non virtuelle? Et s'il y a des cas dans lesquels le compilateur va émettre un callvirt pour une méthode non-virtuelle cela crée-t-il des problèmes pour la sécurité de type?

+1

très bonne question ... m'a fait demander de l'mes livres. – Gishu

+15

Récapitulatif: Case (1) appelle la méthode virtuelle: generate callvirt. Case (2) invoque la méthode de l'instance sur un receveur nullable: generate callvirt pour obtenir une valeur null null - oui, ceci est typeafe. Case (3) appelle une méthode d'instance sur un récepteur non annulable connu: generate appel à _avoid_ null check. Votre premier exemple tombe dans la catégorie (2), votre deuxième exemple tombe dans la catégorie (3). (Le compilateur sait que new ne renvoie jamais null et donc n'a pas besoin de vérifier à nouveau.) –

+0

Merci Eric, la vérification de null a beaucoup de sens. Votre commentaire et la réponse de Gishu rendent les choses beaucoup plus claires! :) –

Répondre

20

Vous jouez simplement en toute sécurité.

Techniquement compilateur C# ne toujours utilisation callvirt

Pour les méthodes statiques & méthodes définies sur les types de valeur, il utilise call. La majorité est fournie via l'instruction IL callvirt. La différence qui a balancé le vote entre les deux est le fait que call suppose que "l'objet utilisé pour faire l'appel" n'est pas nul. D'autre part, callvirt vérifie non null et renvoie une exception NullReferenceException si nécessaire.

  • Pour les méthodes statiques, l'objet est un objet de type et ne peut pas être nul. Idem pour les types de valeur. Par conséquent call est utilisé pour eux - de meilleures performances.
  • Pour les autres, les concepteurs de langage ont décidé d'aller avec callvirt afin que le compilateur JIT vérifie que l'objet utilisé pour effectuer l'appel n'est pas nul. Même pour les méthodes d'instance non-virtuelles, ils apprécient la sécurité par rapport aux performances.

Voir aussi: Jeff Richter fait un meilleur travail à ce - dans son chapitre 'Types Conception' dans CLR via C# 2 Ed

+0

Je pense qu'ils utilisent tous les deux appel avec le débogueur désactivé [(le premier est callvirt dans le débogage parce que vous pouvez placer un point d'arrêt sur la deuxième ligne et changer la valeur de o à null)] (http://stackoverflow.com/a/193955/2850543). –

1

Le compilateur ne connaît pas le type réel o dans la première expression, mais il connaît le type réel dans la deuxième expression. On dirait qu'il ne regarde qu'une seule déclaration à la fois. Ceci est correct, car C# dépend fortement du JIT pour l'optimisation. Il est très probable que dans un cas aussi simple, les deux appels deviendront des appels d'instance à l'exécution.

Je ne crois pas que callvirt soit jamais émis pour des méthodes non virtuelles, mais même si c'était le cas, cela ne poserait aucun problème car la méthode ne serait jamais annulée (pour des raisons évidentes).

+0

Cela explique seulement callvirt pour les méthodes virtuelles. Mais GetType n'est pas virtuel. C'est une fonction externe implémentée quelque part dans les entrailles du CLR (retournant probablement un champ qui est stocké dans le vtable de l'objet ou quelque chose). C'est la même méthode pour chaque objet. – Niki

+0

Assez juste - Je suppose que GetType était virtuel. Ma faute. J'aime la réponse de Dustin. – zildjohn01

0

Je devinerais que c'est parce que le premier affecte à une variable, qui pourrait potentiellement contenir une instance downcasted d'un autre type qui aurait pu remplacer GetType (même si nous pouvons voir que non); le second ne pourrait jamais être autre chose que Object.

27

Voir this ancien article de blog par Eric Gunnerson.

Voici le texte du message:

Pourquoi C# utilisez toujours callvirt?

Cette question a été soulevée sur un alias interne C#, et j'ai pensé que la réponse serait d'intérêt général. C'est en supposant que la réponse est correcte - ça fait longtemps. Le langage .NET IL fournit à la fois une instruction call et callvirt, le callvirt étant utilisé pour appeler des fonctions virtuelles. Mais si vous regardez à travers le code que génère C#, vous verrez qu'il génère un "callvirt" même dans les cas où il n'y a pas de fonction virtuelle impliquée. Pourquoi ça fait ça? Je suis retourné à travers les notes de conception de langue que j'ai, et ils disent très clairement que nous avons décidé d'utiliser callvirt le 13/12/1999. Malheureusement, ils ne tiennent pas compte de notre raison d'être, alors je vais devoir m'en souvenir. Nous avions reçu un rapport de quelqu'un (probablement l'un des groupes .NET utilisant C# (pensait qu'il n'était pas encore nommé C# à ce moment-là)) qui avait écrit un code qui appelait une méthode sur un pointeur nul, mais ils n'a pas eu d'exception car la méthode n'a pas accédé à des champs (ie "this" était nul, mais rien dans la méthode ne l'a utilisé). Cette méthode a ensuite appelé une autre méthode qui a utilisé ce point et a lancé une exception, et un peu de grattage de la tête s'ensuivit. Après qu'ils l'aient compris, ils nous ont envoyé une note à ce sujet.

Nous pensions que pouvoir appeler une méthode sur une instance nulle était un peu bizarre. Peter Golde a fait quelques tests pour voir quel était l'impact du perf qui consistait à toujours utiliser callvirt, et c'était suffisamment petit pour que nous décidions de faire le changement.

+3

Dommage qu'il soit impossible qu'une méthode non-virtuelle puisse spécifier qu'elle devrait être appelée sans 'callvirt' car il aurait été utile de permettre à des méthodes comme' string.IsNullOrEmpty' d'être utilisable sur des chaînes nulles. – supercat

+0

@supercat un moyen de contourner cela est d'utiliser des méthodes d'extension, même si l'objet était nul il serait toujours passé dans le premier paramètre d'extension. – vexe

+0

Malheureusement, vous ne pouvez pas utiliser des méthodes d'extension avec des types génériques dans une fonction générique pour créer une décision d'exécution sur (effectivement) une méthode statique. – NetMage

3

En tant que (perhaps-) côté intéressant ... GetType() est inhabituel en ce que n'est pasvirtual - ce qui conduit à certains very, very odd things.

(marqué comme wiki comme il est un peu hors-sujet à la question réelle)

Questions connexes