重写方法上的C#可选参数

似乎在.NET Framework中,重写该方法时,可选参数存在问题。以下代码的输出是:“ bbb”“ aaa”。但是我期望的输出是:“ bbb”“ bbb”。是否有针对此的解决方案。我知道可以通过方法重载来解决,但想知道这样做的原因。该代码在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();

    }

}


阿波罗的战车
浏览 642回答 3
3回答

慕码人8056858

这里需要注意的一件事是,每次都会调用覆盖的版本。将替代更改为:public override void MyMethod(string s = "bbb"){  Console.Write("derived: ");  base.MyMethod(s);}输出为:derived: bbbderived: aaa类中的方法可以执行以下一项或多项操作:它定义了其他代码调用的接口。它定义了一个在调用时执行的实现。它可能不会两者都做,因为抽象方法只会做前者。在BBB调用MyMethod()调用方法定义在AAA。由于中存在覆盖BBB,因此调用该方法将导致实现BBB被调用。现在,中的定义AAA将两件事告知调用代码(嗯,还有一些无关紧要的地方)。签名void MyMethod(string)。(对于支持该语言的语言)单个参数的默认值为"aaa",因此,MyMethod()如果在找不到该方法匹配的形式的形式的代码时MyMethod(),可以用对MyMethod(“ aaa”)的调用来替换它。因此,这就是调用的BBB作用:编译器看到对的调用MyMethod(),未找到方法,MyMethod()但确实找到了方法MyMethod(string)。它还可以看到在定义它的地方有一个默认值“ aaa”,因此在编译时它将其更改为对的调用MyMethod("aaa")。从内部BBB,即使在中被覆盖,也AAA被认为AAA是定义方法的地方BBB,以便可以覆盖它们。在运行时,MyMethod(string)使用参数“ aaa”调用。因为存在一个覆盖的形式,所以该形式被调用,但是不使用“ bbb”来调用它,因为该值与运行时实现无关,而与编译时定义无关。添加this.更改将检查哪个定义,从而更改调用中使用的参数。编辑:为什么这对我来说似乎更直观。就个人而言,由于我所说的是直观的东西,因此只能是个人的,我发现它更直观,原因如下:如果我正在编码,BBB那么无论是调用还是覆盖MyMethod(string),我都认为这是“正在做的AAA事情”,这是BBB在“正在做的AAA事情” 上做的事情,但实际上AAA都是在做事情。因此,无论是调用还是覆盖,我都将意识到它就是AAA定义的事实MyMethod(string)。如果我要调用使用过的代码BBB,我会想到“使用BBB东西”。我可能不太了解最初是在中定义的AAA,因此我可能会认为这只是实现细节(如果我也没有使用AAA附近的接口)。编译器的行为符合我的直觉,这就是为什么当初读这个问题时,我觉得Mono有一个错误。经考虑,我看不到任何一个如何比另一个更好地实现指定的行为。尽管如此,尽管如此,在保持个人水平的同时,我绝不会将可选参数与抽象,虚拟或覆盖的方法一起使用,并且如果覆盖了别人的方法,我会匹配它们的。

慕的地10843

您可以通过以下方式消除歧义:this.MyMethod();(在中MyMethod2())它是否是一个漏洞是棘手的。它看起来确实不一致。Resharper会警告您根本不要更改覆盖中的默认值,如果这样做会有所帮助; p当然,resharper 还会告诉您this.多余,并愿意为您删除它……这会改变行为-因此,resharper也并不完美。看起来确实有可能成为编译器错误,我将授予您。我需要看真仔细,以确保...这里的埃里克·当你需要他,是吧?编辑:这里的重点是语言规范;让我们看看第7.5.3节:例如,用于方法调用的候选集不包括标记为重写的方法(第7.4节),并且如果派生类中的任何方法均适用(第7.6.5.1节),则基类中的方法也不是候选者。(实际上,第7.4节显然没有override考虑方法)这里有一些冲突...。它指出如果派生类中有适用的方法,则不使用基本方法-这将导致我们进入派生方法,但与此同时,它表示标记override的方法没有考虑过的。但是,第7.5.1.1节指出:对于在类中定义的虚拟方法和索引器,参数列表是从函数成员的最特定的声明或重写中选取的,从接收方的静态类型开始,并搜索其基类。然后第7.5.1.2节解释了在调用时如何评估值:在函数成员调用(第7.5.4节)的运行时处理期间,参数列表的表达式或变量引用按从左到右的顺序求值,如下所示:...(剪断)...当从具有相应可选参数的函数成员中省略参数时,将隐式传递函数成员声明的默认参数。由于这些参数始终是恒定的,因此它们的评估不会影响其余参数的评估顺序。这明确突出表明它正在查看参数列表,该列表先前在§7.5.1.1中定义为来自最具体的声明或重写。似乎这是第7.5.1.2节中提到的“方法声明”是合理的,因此传递的值应该是从最派生的值到静态类型的值。这可能表明:csc有一个错误,并且应该使用派生版本(“ bbb bbb”),除非它被限制(通过base.或强制转换为基本类型)查看基本方法声明(第7.6.8节)。 )。

富国沪深

在我看来,这似乎是个错误。我相信它已被明确指定,并且其行为应与您使用显式this前缀调用该方法的行为相同。我已将示例简化为仅使用单个虚拟方法,并显示了调用了哪种实现以及参数值是什么: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();    }}因此,我们需要担心的是RunTests中的三个调用。前两个调用的规范的重要部分是7.5.1.1节,该节讨论在查找相应参数时要使用的参数列表:对于在类中定义的虚拟方法和索引器,参数列表是从函数成员的最特定的声明或重写中选取的,从接收方的静态类型开始,并搜索其基类。以及7.5.1.2节:当从具有相应可选参数的函数成员中省略参数时,将隐式传递函数成员声明的默认参数。“相应的可选参数”是将7.5.2关联到7.5.1.1的位。对于M()和this.M(),参数列表都应该是Derived接收者的静态类型Derived,实际上,您可以告诉编译器将其视为编译早期的参数列表,就好像您在中都将参数强制设置为一样。调用失败-因此调用要求参数在中具有默认值,但随后将其忽略!Derived.M()M()Derived确实,情况变得更糟:如果您在中提供参数的默认值,Derived但在中将Base其设为必需,则调用 M()最终将null用作参数值。如果没有别的,我想证明这是一个错误:该null值不能来自任何有效的地方。(这是null由于它是string类型的默认值;它始终仅将默认值用于参数类型。)与base.M(),它表示,该规范交易部分7.6.8 ,以及作为非虚拟行为,表达被视为((Base) this).M(); 因此,使用基本方法确定有效参数列表是完全正确的。这意味着最后一行是正确的。只是为了使想要查看上述真正奇怪的错误的人更轻松,其中使用了未在任何地方指定的值: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();    }
打开App,查看更多内容
随时随地看视频慕课网APP