Lorsque le runtime exécute un call
l'instruction consiste à faire un appel à un morceau de code exact (méthode). Il n'y a aucun doute sur l'endroit où il existe. Une fois que l'IL a été JITté, le code machine résultant au niveau du site d'appel est un code inconditionnel jmp
l'instruction.
En revanche, le callvirt
est utilisée pour appeler des méthodes virtuelles de manière polymorphe. L'emplacement exact du code de la méthode doit être déterminé au moment de l'exécution pour chaque invocation. Le code JIT résultant implique une certaine indirection à travers les structures vtable. L'appel est donc plus lent à exécuter, mais il est plus flexible car il permet des appels polymorphes.
Notez que le compilateur peut émettre call
des instructions pour les méthodes virtuelles. Par exemple :
sealed class SealedObject : object
{
public override bool Equals(object o)
{
// ...
}
}
Pensez à appeler le code :
SealedObject a = // ...
object b = // ...
bool equal = a.Equals(b);
Alors que System.Object.Equals(object)
est une méthode virtuelle, dans cette utilisation, il n'y a pas de possibilité de surcharge de la méthode Equals
pour exister. SealedObject
est une classe scellée et ne peut pas avoir de sous-classes.
C'est pour cette raison que l'approche de .NET sealed
peuvent avoir de meilleures performances de répartition des méthodes que leurs homologues non scellés.
EDITAR: Il s'avère que j'avais tort. Le compilateur C# ne peut pas faire un saut inconditionnel vers l'emplacement de la méthode parce que la référence de l'objet (la valeur de this
dans la méthode) peut être nulle. Au lieu de cela, il émet callvirt
qui effectue la vérification de la nullité et lance l'appel si nécessaire.
Cela explique en fait un code bizarre que j'ai trouvé dans le cadre .NET en utilisant Reflector :
if (this==null) // ...
Il est possible pour un compilateur d'émettre un code vérifiable qui a une valeur nulle pour l'attribut this
pointeur (local0), seul csc ne le fait pas.
Donc je suppose call
n'est utilisé que pour les méthodes statiques de classe et les structs.
Compte tenu de ces informations, il me semble maintenant que sealed
n'est utile que pour la sécurité de l'API. J'ai trouvé une autre question qui semble suggérer qu'il n'y a aucun avantage de performance à sceller vos classes.
EDIT 2 : Il y a plus qu'il n'y paraît. Par exemple, le code suivant émet un call
l'instruction :
new SealedObject().Equals("Rubber ducky");
Évidemment, dans un tel cas, il n'y a aucune chance que l'instance de l'objet soit nulle.
Il est intéressant de noter que dans un build DEBUG, le code suivant émet callvirt
:
var o = new SealedObject();
o.Equals("Rubber ducky");
En effet, vous pourriez placer un point d'arrêt sur la deuxième ligne et modifier la valeur de o
. Dans les builds de la version, j'imagine que l'appel serait un call
plutôt que callvirt
.
Malheureusement, mon PC est actuellement hors service, mais je ferai l'expérience dès qu'il sera remis en marche.