77 votes

C# paramètres facultatifs sur les méthodes de remplacement

Semble que dans .NET Framework il y a un problème avec les paramètres facultatifs lorsque vous remplacez la méthode. La sortie du code ci-dessous: "bbb" "aaa" . Mais le résultat que j'attends: "bbb" "bbb" .Est-il une solution pour ce. Je sais que cela peut être résolu avec la surcharge de méthode, mais vous demandez la raison pour cela. Le code fonctionne très bien en Mono.

class Program
{
    class AAA
    {
        public virtual void MyMethod(string s = "aaa")
        {
            Console.WriteLine(s);
        }

        public virtual void MyMethod2()
        {
            MyMethod();
        }
    }

    class BBB : AAA
    {
        public override void MyMethod(string s = "bbb")
        {
            base.MyMethod(s);
        }

        public override void MyMethod2()
        {
            MyMethod();
        }
    }

    static void Main(string[] args)
    {
        BBB asd = new BBB();
        asd.MyMethod();
        asd.MyMethod2();
    }
}

39voto

Marc Gravell Points 482669

Vous pouvez lever l'ambiguïté en appelant:

this.MyMethod();

(en MyMethod2())

Si c'est un bug est difficile; il n'a pas l'air incohérent, si. Resharper vous avertit tout simplement pas avoir des changements à la valeur par défaut dans une substitution, si ça peut aider ;p bien sûr, resharper aussi vous indique l' this. est redondant, et vous propose de supprimer pour vous ... ce qui modifie le comportement de sorte resharper n'est pas parfait.

Il ne ressemble, il pourrait être admissible à titre de compilateur bug, je vous l'accorde. J'avais besoin de regarder vraiment avec soin pour être sûr... où est Eric quand vous avez besoin de lui, hein?


Edit:

Le point clé ici est la langue spec; regardons §7.5.3:

Par exemple, l'ensemble des candidats pour une invocation de méthode ne comporte pas de méthodes marqué remplacer (§7.4), et des méthodes dans une classe de base ne sont pas des candidats si une méthode dans une classe dérivée est applicable (§7.6.5.1).

(et de fait, §7.4 clairement omet override méthodes de l'examen)

Il y a un conflit ici.... il indique que la base de méthodes ne sont pas utilisées lorsqu'il y a une méthode dans une classe dérivée - ce qui nous conduirait à la dérivée de la méthode, mais en même temps, il est dit que les méthodes marquées override ne sont pas considérées.

Mais, §7.5.1.1 puis déclare:

Pour les méthodes virtuelles et des indexeurs définies dans les classes, la liste des paramètres est choisi à partir de la plus spécifique de la déclaration ou de substitution de la fonction de membre, en commençant par le type statique du receveur, et à la recherche de ses classes de base.

et puis, §7.5.1.2 explique comment les valeurs sont évaluées au moment de l'invoquer:

Au cours de l'exécution du traitement d'une fonction membre de l'invocation (§7.5.4), les expressions ou les références à des variables d'une liste d'arguments sont évalués dans l'ordre, de gauche à droite, comme suit:

...(snip)...

Lorsque les arguments sont omis à partir d'une fonction membre correspondant des paramètres facultatifs, la valeur par défaut des arguments de la fonction de déclaration d'un membre sont implicitement passé. Parce que ce sont toujours constante, leur évaluation aura pas d'incidence sur l'ordre d'évaluation des arguments restants.

Cette explicitement souligne que c'est en regardant la liste d'arguments, qui a déjà été défini dans le §7.5.1.1 comme venant de la plus spécifique de la déclaration ou de la remplacer. Il semble raisonnable que c'est la "déclaration de méthode" qui est visé dans le §7.5.1.2, donc la valeur transmise doit être de la plus dérivée à le type statique.

Cela donnerait à penser: le scc a un bug, et il devrait être à l'aide de la dérivée de la version ("bbb bbb"), sauf si elle est limitée (par base., ou de coulée de base-type) à la recherche à la base des déclarations de méthode (§7.6.8).

28voto

Jon Hanna Points 40291

Une chose à noter ici est que la version de remplacement est appelée à chaque fois. Changer le remplacer:

public override void MyMethod(string s = "bbb")
{
  Console.Write("derived: ");
  base.MyMethod(s);
}

Et la sortie est:

derived: bbb
derived: aaa

Une méthode dans une classe peut faire un ou deux des éléments suivants:

  1. Il définit une interface pour un autre code d'appel.
  2. Il définit une mise en œuvre pour exécuter lors de l'appel.

Il ne peut pas faire les deux, comme une méthode abstraite ne s'occupe que de l'ancien.

Au sein d' BBB l'appel MyMethod() appelle une méthode définie en AAA.

Parce qu'il ya un remplacement en BBB, en appelant cette méthode aboutit à une mise en œuvre en BBB de la demande.

Maintenant, la définition de l' AAA informe le code appelant, de deux choses l'une (enfin, quelques autres encore qui n'ont pas d'importance ici).

  1. La signature void MyMethod(string).
  2. (Pour les langues qui le prennent en charge) la valeur par défaut pour le paramètre "aaa" et, par conséquent, lors de la compilation d'un code de la forme MyMethod() si aucune méthode d'appariement MyMethod() peut être trouvé, vous pouvez le remplacer par un appel à " MyMethod("aaa").

Donc, c'est ce que l'appel en BBB fait: Le compilateur voit un appel à l' MyMethod(), ne trouve pas une méthode MyMethod() mais n'trouver une méthode MyMethod(string). Il voit aussi qu'à l'endroit où il a été défini il y a une valeur par défaut de "aaa", donc au moment de la compilation, ça change de ce à un appel à l' MyMethod("aaa").

À partir de l'intérieur d' BBB, AAA est considéré comme le lieu où l' AAA's méthodes sont définies, même si substituée en BBB, de sorte qu'ils peuvent être ignoré.

Au moment de l'exécution, MyMethod(string) est appelée avec l'argument "aaa". Parce qu'il n'y est substituée à la forme, qui est la forme dite, mais il n'est pas appelé à "bbb", parce que la valeur n'a rien à voir avec le moment de l'exécution de la mise en œuvre, mais avec le moment de la compilation définition.

L'ajout d' this. des changements de définition qui est examiné, et donc les changements que argument est utilisé dans l'appel.

Edit: Pourquoi ce qui semble le plus intuitif pour moi.

Personnellement, et depuis que je suis à parler de ce qui est intuitif, il ne peut être personnel, je trouve cela plus intuitive pour la raison suivante:

Si j'étais codage BBB alors que l'appel ou de la transgression MyMethod(string), je pense, en tant que "faisant AAA trucs" - BBBs prendre sur de "faire l' AAA trucs", mais elle fait AAA trucs tout de même. Par conséquent, si l'appel ou de la transgression, je vais être conscient du fait qu'il a été AAA que définis MyMethod(string).

Si j'étais le code appelant, qui a utilisé BBB, je pense à "utiliser BBB trucs". Je ne pourrais pas être très au courant de ce qui a été défini dans AAA,, et je n'aurais peut-être penser à cela comme un simple détail de l'implémentation (si je n'ai pas également utiliser l' AAA interface de proximité).

Le compilateur du comportement correspond à mon intuition, c'est pourquoi lors de la première lecture de la question, il me semblait que les Mono avait un bug. Après réflexion, je ne vois pas comment, soit remplit les comportements spécifiques, mieux que les autres.

Pour que la matière même si, tout en restant à un niveau personnel, je n'avais jamais utiliser les paramètres facultatifs dans l'abstrait, virtuel ou de méthodes de remplacement, et si une substitution de quelqu'un d'autre qui l'a fait, j'aimerais correspondre à la leur.

16voto

Jon Skeet Points 692016

Cela ressemble à un bug pour moi. Je crois qu'il est bien spécifié, et qu'il devrait se comporter de la même manière que si vous appelez la méthode explicite this préfixe.

J'ai simplifié l'exemple pour n'utiliser qu'un seulvirtuelle méthode, et de montrer à la fois ce qui est appelé et la mise en œuvre ce que la valeur du paramètre est:

using System;

class Base
{
    public virtual void M(string text = "base-default")
    {
        Console.WriteLine("Base.M: {0}", text);
    }   
}

class Derived : Base
{
    public override void M(string text = "derived-default")
    {
        Console.WriteLine("Derived.M: {0}", text);
    }

    public void RunTests()
    {
        M();      // Prints Derived.M: base-default
        this.M(); // Prints Derived.M: derived-default
        base.M(); // Prints Base.M: base-default
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.RunTests();
    }
}

De sorte que tous nous avons besoin de s'inquiéter sont les trois appels dans un délai de RunTests. Les choses importantes de la spec pour les deux premiers appels sont section 7.5.1.1, qui parle de la liste des paramètres à être utilisé lors de la recherche des paramètres correspondants:

Pour les méthodes virtuelles et des indexeurs définies dans les classes, le paramètre la liste est choisi à partir de la plus spécifique de la déclaration ou de la remplacer de la fonction membre, en commençant par le type statique de l' récepteur, et à la recherche de ses classes de base.

Et de l'article 7.5.1.2:

Lorsque les arguments sont omis à partir d'une fonction membre correspondant des paramètres facultatifs, la valeur par défaut des arguments de la fonction de déclaration d'un membre sont implicitement passé.

Le "correspondant paramètre optionnel" est le bit qui lie 7.5.2 pour 7.5.1.1.

Pour les deux M() et this.M(), ce paramètre liste devrait être l'un dans l' Derived comme type statique du receveur est - Derived, En effet, vous pouvez dire que le compilateur traite que la liste des paramètres plus tôt dans la compilation, comme si vous rendre le paramètre obligatoire , en Derived.M(), à la fois de la appels échouent - de sorte que l' M() appel exige le paramètre d'avoir une valeur par défaut dans Derived, mais ensuite l'ignore!

En effet, il y a pire: si vous fournissez une valeur par défaut pour le paramètre Derived mais le rendre obligatoire, en Base, l'appel M() finit à l'aide de null la valeur de l'argument. Si rien d'autre, Je dirais que prouve qu'il s'agit d'un bug: c' null de la valeur ne peut pas venir à partir de n'importe où valide. ( null Due à celle de la valeur par défaut la valeur de l' string type; il est toujours juste utilise la valeur par défaut pour le type de paramètre.)

Section 7.6.8 de la spécification traite avec la base.M(), qui dit que ainsi que la non-virtuel le comportement, l'expression est considérée comme en tant que ((Base) this).M(); il est donc tout à fait correct pour la méthode de base à être utilisés pour déterminer l'efficacité de la liste des paramètres. Cela signifie que la dernière ligne est correcte.

Juste pour rendre les choses plus facile pour ceux qui veulent voir le très étrange bogue décrit ci-dessus, lorsqu'une valeur n'est spécifié nulle part est utilisée:

using System;

class Base
{
    public virtual void M(int x)
    {
        // This isn't called
    }   
}

class Derived : Base
{
    public override void M(int x = 5)
    {
        Console.WriteLine("Derived.M: {0}", x);
    }

    public void RunTests()
    {
        M();      // Prints Derived.M: 0
    }

    static void Main()
    {
        new Derived().RunTests();
    }
}

10voto

chiffre Points 1601

Avez-vous essayé:

 public override void MyMethod2()
    {
        this.MyMethod();
    }

Donc en fait vous dites à votre programme d'utilisation de la Méthode surchargée.

10voto

Eric Lippert Points 300275

Le comportement est certainement très étrange; il n'est pas clair pour moi si il est en fait un bug du compilateur, mais il pourrait être.

Le campus a obtenu une bonne quantité de neige la nuit dernière et Seattle n'est pas très bonne sur la façon de traiter avec de la neige. Mon bus n'est pas exécuté ce matin, donc je ne vais pas être en mesure d'entrer dans le bureau de comparer ce que le C# 4 C# 5 et Roslyn dire à propos de ce cas et si ils sont en désaccord. Je vais essayer de poster une analyse plus tard cette semaine, une fois que je suis de retour dans le bureau et peut utiliser les bons outils de débogage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X