为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

千巷猫影 2019-12-25 15:46:14
似乎在.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();    }}
查看完整描述

3 回答

?
慕少森

TA贡献2019条经验 获得超9个赞

这里需要注意的一件事是,每次都会调用覆盖的版本。将替代更改为:


public override void MyMethod(string s = "bbb")

{

  Console.Write("derived: ");

  base.MyMethod(s);

}

输出为:


derived: bbb

derived: 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有一个错误。经考虑,我看不到任何一个如何比另一个更好地实现指定的行为。


尽管如此,尽管如此,在保持个人水平的同时,我绝不会将可选参数与抽象,虚拟或重写方法一起使用,并且如果要覆盖其他人使用的参数,我会匹配它们。


查看完整回答
反对 回复 2019-12-25
?
慕娘9325324

TA贡献1783条经验 获得超4个赞

您可以通过以下方式消除歧义:


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节)。 )。


查看完整回答
反对 回复 2019-12-25
?
PIPIONE

TA贡献1829条经验 获得超9个赞

在我看来,这似乎是个错误。我相信它已被明确指定,并且其行为应与您使用显式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();

    }

}


查看完整回答
反对 回复 2019-12-25
  • 3 回答
  • 0 关注
  • 467 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信