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?

6
demandé sur Denis 2015-12-16 19:59:44

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'Extension

ont é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.

8
répondu Eric Lippert 2015-12-16 22:03:34

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.

6
répondu dasblinkenlight 2015-12-16 17:19:50

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.

3
répondu René Vogt 2015-12-16 17:20:35