Méthode statique et méthode d'extension avec le même nom
j'ai créé la méthode d'extension:
public static class XDecimal
{
public static decimal Floor(
this decimal value,
int precision)
{
decimal step = (decimal)Math.Pow(10, precision);
return decimal.Floor(step * value) / step;
}
}
Maintenant, j'essaie de l'utiliser:
(10.1234m).Floor(2)
mais le compilateur dit Member 'decimal.Floor(decimal)' cannot be accessed with an instance reference; qualify it with a type name instead
. Je comprends qu'il y a la méthode statique decimal.Floor(decimal)
. Mais il a une signature différente. Pourquoi le compilateur est incapable de choisir la bonne méthode?
3 réponses
vous avez deux bonnes et correctes réponses ici, mais je comprends que les réponses qui citent simplement la spécification ne sont pas toujours aussi éclairants. Permettez-moi d'ajouter quelques détails supplémentaires.
vous avez probablement un modèle mental de résolution de surcharge qui va comme ceci:
- mettez toutes les méthodes possibles dans un grand seau -- méthodes d'extension, méthodes statiques, méthodes d'instance, etc.
- S'il existe des méthodes ce serait une erreur de les éliminer du seau.
- parmi les méthodes restantes, choisissez celle qui a la meilleure correspondance entre les expressions d'arguments et les types de paramètres.
bien qu'il s'agisse du modèle mental de résolution de la surcharge de nombreux individus, malheureusement, il est subtilement faux.
le vrai modèle -- et je vais ignorer les problèmes d'inférence de type générique ici -- est le suivant:
- Put toutes les instances et les méthodes statiques dans un seau. Les dérogations virtuelles ne sont pas comptées comme des méthodes d'instance.
- élimine les méthodes qui sont inapplicables parce que les arguments ne correspondent pas aux paramètres.
À ce stade, nous avons des méthodes dans le seau ou nous ne faisons pas. Si nous avons n'importe quelles méthodes dans le seau alors méthodes d'extension ne sont pas vérifiées . C'est ce qui importe peu ici. Le modèle est Non "si une résolution de surcharge normale a produit une erreur, nous vérifions les méthodes d'extension". Le modèle est le suivant:"si la résolution normale de la surcharge n'a produit aucune méthode applicable, quelle qu'elle soit, nous vérifions les méthodes d'extension".
S'il y a des méthodes dans le seau alors il y a plus d'élimination des méthodes de classe de base, et finalement la meilleure méthode est choisie en fonction de la façon dont les arguments correspondent aux paramètres.
si cela arrive à choisir une méthode statique puis C# supposera que vous vouliez utiliser le nom de type et utilisé une instance par erreur , pas que vous souhaitez rechercher une méthode d'extension . La résolution de surcharge a déjà déterminé qu'il y a une instance ou une méthode statique dont les paramètres correspondent aux arguments que vous avez donnés, et il va soit choisir l'un d'eux ou donner une erreur; il ne va pas dire "oh, vous avez probablement voulu appeler cette méthode d'extension farfelue que arrive juste à être dans la portée".
je comprends que c'est frustrant de votre point de vue. Vous souhaitez clairement que le modèle soit "si la résolution de la surcharge produit une erreur, revenez aux méthodes d'extension". Dans votre exemple, ce serait utile, mais ce comportement produit de mauvais résultats dans les autres scénarios. Par exemple, supposons que vous ayez quelque chose comme
mystring.Join(foo, bar);
L'erreur donnée ici est qu'il doit être string.Join
. Ce serait bizarre si le C# compilateur dit "oh, string.Join
est statique. L'utilisateur a probablement voulu utiliser la méthode d'extension qui n'des jointures sur des séquences de caractères, laissez-moi essayer..."et puis vous avez reçu un message d'erreur disant que l'opérateur sequence join -- qui n'a absolument rien à voir avec votre code ici -- n'a pas les bons arguments.
ou pire, si par quelque miracle vous a fait lui donner des arguments qui ont fonctionné mais ont voulu la méthode statique à appeler, alors votre code serait cassé d'une manière très bizarre et difficile à déboguer.
Les méthodes D'Extensionont été ajoutées très tard dans le jeu et les règles pour les rechercher les font délibérément préférer donner des erreurs à travailler magiquement. Il s'agit d'un système de sécurité qui garantit que les méthodes d'extension ne sont pas liées par accident.
le processus de décision sur la méthode d'appel comporte de nombreux petits détails décrits dans la spécification linguistique C#. Le point clé applicable à votre scénario est que les méthodes d'extension ne sont considérées pour l'invocation que lorsque le compilateur ne peut trouver une méthode à appeler parmi les méthodes du type récepteur lui-même (i.e. le decimal
).
Voici la partie pertinente de la spécification:
L'ensemble des candidats méthodes pour l'invocation de la méthode est construite. Pour chaque méthode F associée au groupe de méthodes M:
Si F n'est pas Générique, F est un candidat lorsque:
M n'a pas de liste d'arguments de type, et
F est applicable en ce qui concerne A (§7.5.3.1).
selon ce qui précède, double.Floor(decimal)
est un candidat valable.
si l'ensemble résultant des méthodes candidates est vide, alors le traitement ultérieur le long des étapes suivantes est abandonné, et à la place une tentative est faite pour traiter l'invocation comme une invocation de méthode d'extension (§7.6.5.2). En cas d'échec, il n'existe aucune méthode applicable et une erreur de temps contraignant se produit.
dans votre cas, l'ensemble des méthodes candidates n'est pas vide, donc les méthodes d'extension ne sont pas considérées.
la signature de decimal.Floor
est
public static Decimal Floor(Decimal d);
Je ne suis pas un spécialiste de l'inférence de type, mais je suppose que puisqu'il y a une conversion implicite de int
en Decimal
le compilateur choisit cette méthode comme la meilleure.
si vous changez votre signature en
public static decimal Floor(
this decimal value,
double precision)
et appelez ça comme
(10.1234m).Floor(2d)
ça marche. Mais bien sûr un double
car la précision est un peu étrange.
EDIT: une citation de Eric Lippert on the alogrithm:
toute méthode du type récepteur est plus proche que toute méthode d'extension.
Floor
est une méthode du "type récepteur" ( Decimal
). Sur le pourquoi les développeurs C# l'ont implémenté comme ceci Je ne peux pas faire de déclaration.