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

发布于 2024-12-27 10:17:58 字数 762 浏览 0 评论 0原文

似乎在 .NET Framework 中,当您重写方法时,可选参数存在问题。下面代码的输出是: “bb” “啊啊” 。但我期望的输出是: “bb” “bb” .有没有办法解决这个问题。我知道可以通过方法重载来解决这个问题,但想知道这样做的原因。该代码在 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();
    }
}

Seems like in .NET Framework there is an issue with optional parameters when you override the method. The output of the code below is:
"bbb"
"aaa"
. But the output I'm expecting is:
"bbb"
"bbb"
.Is there a solution for this. I know it can be solved with method overloading but wondering the reason for this. Also the code works fine in 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();
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

╰沐子 2025-01-03 10:17:59

您可以通过调用以下方法来消除歧义:(

this.MyMethod();

MyMethod2() 中)

是否是一个 bug 很棘手;不过,它看起来确实不一致。 ReSharper 会警告您不要更改覆盖中的默认值(如果这有帮助);p 当然,ReSharper 会告诉您 this. 是多余的,并提供为您删除它...这会改变行为 - 所以 ReSharper 也并不完美。

我承认,它看起来确实可以被视为编译器错误。我需要非常仔细查看才能确定……当你需要埃里克时他在哪里,是吗?


编辑:

这里的关键点是语言规范;让我们看看§7.5.3:

例如,方法调用的候选集不包括标记为 override 的方法(第 7.4 节),并且如果派生类中的任何方法适用,则基类中的方法不是候选方法(第 7.6.5.1 节)。

(事实上​​,§7.4 明确地忽略了 override 方法)

这里存在一些冲突......它指出,如果有适用的方法,则不使用 base 方法派生类 - 这将引导我们使用派生方法,但同时,它表示不考虑标记为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 节)。

You can disambiguate by calling:

this.MyMethod();

(in MyMethod2())

Whether it is a bug is tricky; it does look inconsistent, though. ReSharper warns you simply not to have changes to the default value in an override, if that helps ;p Of course, ReSharper also tells you the this. is redundant, and offers to remove it for you ... which changes the behaviour - so ReSharper also isn't perfect.

It does look like it could qualify as a compiler bug, I'll grant you. I'd need to look really carefully to be sure... where's Eric when you need him, eh?


Edit:

The key point here is the language spec; let's look at §7.5.3:

For example, the set of candidates for a method invocation does not include methods marked override (§7.4), and methods in a base class are not candidates if any method in a derived class is applicable (§7.6.5.1).

(and indeed §7.4 clearly omits override methods from consideration)

There's some conflict here.... it states that the base methods are not used if there is an applicable method in a derived class - which would lead us to the derived method, but at the same time, it says that methods marked override are not considered.

But, §7.5.1.1 then states:

For virtual methods and indexers defined in classes, the parameter list is picked from the most specific declaration or override of the function member, starting with the static type of the receiver, and searching through its base classes.

and then §7.5.1.2 explains how the values are evaluated at the time of the invoke:

During the run-time processing of a function member invocation (§7.5.4), the expressions or variable references of an argument list are evaluated in order, from left to right, as follows:

...(snip)...

When arguments are omitted from a function member with corresponding optional parameters, the default arguments of the function member declaration are implicitly passed. Because these are always constant, their evaluation will not impact the evaluation order of the remaining arguments.

This explicitly highlights that it is looking at the argument list, which was previously defined in §7.5.1.1 as coming from the most specific declaration or override. It seems reasonable that this is the "method declaration" that is referred to in §7.5.1.2, thus the value passed should be from the most derived up-to the static type.

This would suggest: csc has a bug, and it should be using the derived version ("bbb bbb") unless it is restricted (via base., or casting to a base-type) to looking at the base method declarations (§7.6.8).

绅士风度i 2025-01-03 10:17:59

这里值得注意的一件事是每次都会调用重写的版本。将覆盖更改为:

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

输出为:

derived: bbb
derived: aaa

类中的方法可以执行以下一项或两项操作:

  1. 它定义一个接口供其他代码调用。
  2. 它定义了调用时要执行的实现。

它可能不会同时执行两者,因为抽象方法只执行前者。

BBB 中,调用 MyMethod() 调用 AAA定义的方法。

由于 BBB 中存在重写,因此调用该方法会导致调用 BBB 中的实现。

现在,AAA 中的定义告知调用代码两件事(好吧,还有其他一些在这里并不重要的事情)。

  1. 签名void MyMethod(string)
  2. (对于支持它的语言)单个参数的默认值为 "aaa",因此在编译 MyMethod() 形式的代码时,如果没有方法匹配 MyMethod() 可以找到,您可以将其替换为对 `MyMethod("aaa") 的调用。

所以,这就是 BBB 中的调用所做的:编译器看到对 MyMethod() 的调用,但没有找到方法 MyMethod()但确实找到了一个方法MyMethod(string)。它还发现在定义它的地方有一个默认值“aaa”,因此在编译时它将其更改为对 MyMethod("aaa") 的调用。

BBB 内部,AAA 被视为定义 AAA 方法的位置,即使在 BBB 中被覆盖,以便它们可以被覆盖。

在运行时,使用参数“aaa”调用 MyMethod(string)。因为有一个被重写的形式,即被调用的形式,但它不是用“bbb”调用的,因为该值与运行时实现无关,而是与编译时定义有关。

添加 this. 会更改检查的定义,从而更改调用中使用的参数。

编辑:为什么这对我来说似乎更直观。

就我个人而言,由于我谈论的是直观的,所以它只能是个人的,我发现这更直观,原因如下:

如果我正在编码 BBB ,那么无论是调用还是重写 MyMethod( string),我认为这是“做 AAA 东西” - 这是 BBB 的“做 AAA 东西” “,但它正在做AAA 东西都是一样的。因此,无论是调用还是重写,我都会意识到 AAA 定义了 MyMethod(string)

如果我调用使用 BBB 的代码,我会想到“使用 BBB stuff”。我可能不太清楚 AAA 最初定义的是哪个,我可能会认为这仅仅是一个实现细节(如果我没有同时使用 AAA ) > 接口附近)。

编译器的行为符合我的直觉,这就是为什么当我第一次阅读这个问题时,我觉得 Mono 有一个错误。经过考虑,我看不出其中任何一个如何比另一个更好地实现指定的行为。

不过,就这一点而言,在保持个人水平的同时,我永远不会将可选参数与抽象、虚拟或重写方法一起使用,并且如果重写其他人的方法,我会匹配他们的参数。

One thing worth noting here, is that the overridden version is called each time. Change the override to:

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

And the output is:

derived: bbb
derived: aaa

A method in a class can do one or two of the following:

  1. It defines an interface for other code to call.
  2. It defines an implementation to execute when called.

It may not do both, as an abstract method does only the former.

Within BBB the call MyMethod() calls a method defined in AAA.

Because there is an override in BBB, calling that method results in an implementation in BBB being called.

Now, the definition in AAA informs calling code of two things (well, a few others too that don't matter here).

  1. The signature void MyMethod(string).
  2. (For those languages that support it) the default value for the single parameter is "aaa" and therefore when compiling code of the form MyMethod() if no method matching MyMethod() can be found, you may replace it with a call to `MyMethod("aaa").

So, that's what the call in BBB does: The compiler sees a call to MyMethod(), doesn't find a method MyMethod() but does find a method MyMethod(string). It also sees that at the place where it is defined there's a default value of "aaa", so at compile time it changes this to a call to MyMethod("aaa").

From within BBB, AAA is considered the place where AAA's methods are defined, even if overridden in BBB, so that they can be over-ridden.

At run-time, MyMethod(string) is called with the argument "aaa". Because there is a overridden form, that is the form called, but it is not called with "bbb" because that value has nothing to do with the run-time implementation but with the compile-time definition.

Adding this. changes which definition is examined, and so changes what argument is used in the call.

Edit: Why this seems more intuitive to me.

Personally, and since I'm talking of what is intuitive it can only be personal, I find this more intuitive for the following reason:

If I was coding BBB then whether calling or overriding MyMethod(string), I'd think of that as "doing AAA stuff" - it's BBBs take on "doing AAA stuff", but it's doing AAA stuff all the same. Hence whether calling or overriding, I'm going to be aware of the fact that it was AAA that defined MyMethod(string).

If I was calling code that used BBB, I'd think of "using BBB stuff". I might not be very aware of which was originally defined in AAA, and I'd perhaps think of this as merely an implementation detail (if I didn't also use the AAA interface nearby).

The compiler's behaviour matches my intuition, which is why when first reading the question it seemed to me that Mono had a bug. Upon consideration, I can't see how either fulfils the specified behaviour better than the other.

For that matter though, while remaining at a personal level, I'd never use optional parameters with abstract, virtual or overridden methods, and if overriding someone else's that did, I'd match theirs.

绿萝 2025-01-03 10:17:59

这对我来说看起来像是一个错误。我相信它已经明确指定了,
并且它的行为应该与您调用相同的方式
带有显式 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 类型的值;它总是只使用默认值
参数类型。)

规范第 7.6.8 节涉及 base.M(),其中表示
以及与非虚拟行为一样,该表达式也被视为
作为((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();
    }
}

This looks like a bug to me. I believe it is well specified,
and that it should behave in the same way as if you call the
method with the explicit this prefix.

I've simplified the example to only use a single virtual
method, and show both which implementation is called and
what the parameter value is:

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();
    }
}

So all we need to worry about are the three calls within RunTests.
The important bits of the spec for the first two calls are section
7.5.1.1, which talks about the parameter list to be used when finding corresponding parameters:

For virtual methods and indexers defined in classes, the parameter
list is picked from the most specific declaration or override
of the function member, starting with the static type of the
receiver, and searching through its base classes.

And section 7.5.1.2:

When arguments are omitted from a function member with corresponding optional parameters, the default arguments of the function member declaration are implicitly passed.

The "corresponding optional parameter" is the bit that ties 7.5.2 to 7.5.1.1.

For both M() and this.M(), that parameter list should be
the one in Derived as static type of the receiver is Derived,
Indeed, you can tell that the compiler treats
that as the parameter list earlier in the compilation, as if you
make the parameter mandatory in Derived.M(), both of the
calls fail - so the M() call requires the parameter to have
a default value in Derived, but then ignores it!

Indeed, it gets worse: if you provide a default value for the
parameter in Derived but make it mandatory in Base, the call
M() ends up using null as the argument value. If nothing else,
I'd say that proves it's a bug: that null value can't come
from anywhere valid. (It's null due to that being the default
value of the string type; it always just uses the default value
for the parameter type.)

Section 7.6.8 of the spec deals with base.M(), which says that
as well as the non-virtual behaviour, the expression is considered
as ((Base) this).M(); so it's entirely correct for the base method
to be used to determine the effective parameter list. That means
the final line is correct.

Just to make things easier for anyone who wants to see the really odd bug described above, where a value not specified anywhere is used:

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();
    }
}
浮世清欢 2025-01-03 10:17:59

您是否尝试过:

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

所以您实际上告诉您的程序使用重写方法。

Have you tried:

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

So you actually tell your program to use the overriden Method.

嗳卜坏 2025-01-03 10:17:59

这种行为确实很奇怪;我不清楚这是否实际上是编译器中的错误,但可能是。

昨晚校园下了相当大的雪,西雅图不太擅长应对雪。我的公共汽车今天早上没有运行,因此我无法进入办公室比较 C# 4、C# 5 和 Roslyn 对于此案例的看法以及他们是否不同意。一旦我回到办公室并且可以使用适当的调试工具,我将在本周晚些时候尝试发布分析。

The behaviour is definitely very strange; it is not clear to me if it is in fact a bug in the compiler, but it might be.

The campus got a fair amount of snow last night and Seattle is not very good about dealing with snow. My bus is not running this morning so I'm not going to be able to get into the office to compare what C# 4, C# 5 and Roslyn say about this case and if they disagree. I'll try to post an analysis later this week once I'm back in the office and can use proper debugging tools.

染年凉城似染瑾 2025-01-03 10:17:59

这可能是由于歧义造成的,编译器优先考虑基类/超类。下面对 BBB 类的代码进行了更改,添加了对 this 关键字的引用,给出了输出“bbb bbb”:

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

    public override void MyMethod2()
    {
        this.MyMethod(); //added this keyword here
    }
}

它暗示的一件事是您应该始终使用 this 关键字作为最佳实践

我会担心基本方法和子方法中的这种歧义甚至没有引发编译器警告(如果不是错误),但如果确实如此,我想这是看不见的。

====== =================================================== ==========

编辑:请考虑以下链接中的示例摘录:

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/07/28/c.net-little-pitfalls-default-parameters-are-compile-time-substitutions.aspx

<一个href="http://geekswithblogs.net/BlackRabbitCoder/archive/2010/06/17/c-Optional-parameters---pros-and-pitfalls.aspx" rel="nofollow">http://geekswithblogs.net /BlackRabbitCoder/archive/2010/06/17/c-可选参数---pros-and-pitfalls.aspx

陷阱:可选参数值是编译时的
使用可选参数时,需要记住一件事,并且只需要记住一件事。如果您牢记这一点,您很可能会很好地理解并避免使用它们的任何潜在陷阱:
有一件事是这样的:可选参数是编译时的语法糖!

陷阱:注意继承和接口实现中的默认参数

现在,第二个潜在的陷阱与继承和接口实现有关。我将用一个谜题来说明:

   1: public interface ITag 
   2: {
   3:     void WriteTag(string tagName = "ITag");
   4: } 
   5:  
   6: public class BaseTag : ITag 
   7: {
   8:     public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
   9: } 
  10:  
  11: public class SubTag : BaseTag 
  12: {
  13:     public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
  14: } 
  15:  
  16: public static class Program 
  17: {
  18:     public static void Main() 
  19:     {
  20:         SubTag subTag = new SubTag();
  21:         BaseTag subByBaseTag = subTag;
  22:         ITag subByInterfaceTag = subTag; 
  23:  
  24:         // what happens here?
  25:         subTag.WriteTag();       
  26:         subByBaseTag.WriteTag(); 
  27:         subByInterfaceTag.WriteTag(); 
  28:     }
  29: } 

会发生什么?好吧,即使每种情况下的对象都是 SubTag,其标签是“SubTag”,你也会得到:

1: SubTag
2:基本标签
3:ITag

但请记住确保:

不要在现有默认参数集的中间插入新的默认参数,这可能会导致不可预测的行为,不一定会引发语法错误 - 添加到列表末尾或创建新方法。
在继承层次结构和接口中使用默认参数时要格外小心——根据预期用途选择最合适的级别来添加默认值。

=================================================== =======================

May be this is due to ambiguity and the compiler is giving priority to the base/super class. The below change to code of your class BBB with adding reference to this keyword, gives the output 'bbb bbb':

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

    public override void MyMethod2()
    {
        this.MyMethod(); //added this keyword here
    }
}

One of the things it implies is you should always use the this keyword whenever you are calling properties or methods on the current instance of class as a best practice.

I would be concerned if this ambiguity in base and child method didn't even raise a compiler warning (if not error), but if it does then that was unseen I suppose.

==================================================================

EDIT: Consider below sample excerpts from these links:

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/07/28/c.net-little-pitfalls-default-parameters-are-compile-time-substitutions.aspx

http://geekswithblogs.net/BlackRabbitCoder/archive/2010/06/17/c-optional-parameters---pros-and-pitfalls.aspx

Pitfall: Optional parameter values are compile-time
There is one thing and one thing only to keep in mind when using optional parameters. If you keep this one thing in mind, chances are you may well understand and avoid any potential pitfalls with their usage:
That one thing is this: optional parameters are compile-time, syntactical sugar!

Pitfall: Beware of Default Parameters in Inheritance and Interface Implementation

Now, the second potential pitfalls has to do with inheritance and interface implementation. I’ll illustrate with a puzzle:

   1: public interface ITag 
   2: {
   3:     void WriteTag(string tagName = "ITag");
   4: } 
   5:  
   6: public class BaseTag : ITag 
   7: {
   8:     public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
   9: } 
  10:  
  11: public class SubTag : BaseTag 
  12: {
  13:     public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
  14: } 
  15:  
  16: public static class Program 
  17: {
  18:     public static void Main() 
  19:     {
  20:         SubTag subTag = new SubTag();
  21:         BaseTag subByBaseTag = subTag;
  22:         ITag subByInterfaceTag = subTag; 
  23:  
  24:         // what happens here?
  25:         subTag.WriteTag();       
  26:         subByBaseTag.WriteTag(); 
  27:         subByInterfaceTag.WriteTag(); 
  28:     }
  29: } 

What happens? Well, even though the object in each case is SubTag whose tag is “SubTag”, you will get:

1: SubTag
2: BaseTag
3: ITag

But remember to make sure you:

Do not insert new default parameters in the middle of an existing set of default parameters, this may cause unpredictable behavior that may not necessarily throw a syntax error – add to end of list or create new method.
Be extremely careful how you use default parameters in inheritance hierarchies and interfaces – choose the most appropriate level to add the defaults based on expected usage.

==========================================================================

淡莣 2025-01-03 10:17:59

我认为这是因为这些默认值在编译时是固定的。如果您使用反射器,您将在 BBB 中看到 MyMethod2 的以下内容。

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

This I think is because these default values are fixed at the compile time. If you use reflector you will see the following for MyMethod2 in BBB.

public override void MyMethod2()
{
    this.MyMethod("aaa");
}
ま昔日黯然 2025-01-03 10:17:59

总体同意@Marc Gravell 的观点。

不过,我想提一下,这个问题在 C++ 世界中已经足够老了(http://www .devx.com/tips/Tip/12737),答案看起来像“与在运行时解析的虚拟函数不同,默认参数是静态解析的,即在编译时解析的。”因此,这种 C# 编译器行为由于一致性而被故意接受,尽管它似乎是出乎意料的。

Agree in general with @Marc Gravell.

However, I'd like to mention that the issue is old enough in C++ world (http://www.devx.com/tips/Tip/12737), and the answer looks like "unlike virtual functions, which are resolved at run time, default arguments are resolved statically, that is, at compiled time." So this C# compiler behavior had rather been accepted deliberately due to consistency, despite its unexpectedness, it seems.

玉环 2025-01-03 10:17:59

无论哪种方式都需要修复

我肯定会认为它是一个错误,要么因为结果是错误的,要么如果结果是预期的那么编译器不应该让你将它声明为“覆盖”,或者在至少提供警告。

我建议您向 Microsoft.Connect 报告此问题

这是对是错?

但是关于这是否是预期行为,让我们首先分析一下对此的两种观点。

考虑我们有以下代码:

void myfunc(int optional = 5){ /* Some code here*/ } //Function implementation
myfunc(); //Call using the default arguments

有两种方法来实现它:

  1. 可选参数被视为重载函数,导致以下结果:

    void myfunc(int optional){ /* 这里有一些代码*/ } //函数实现
    无效 myfunc(){ myfunc(5); } //默认参数实现
    myfunc(); //使用默认参数调用
    
  2. 默认值嵌入到调用者中,从而产生以下代码:

    void myfunc(int optional){ /* 这里有一些代码*/ } //函数实现
    myfunc(5); //调用并嵌入默认参数
    

两种方法之间有很多差异,但我们首先看一下 .Net 框架如何解释它。

  1. 在.Net中,您只能使用包含相同数量参数的方法来重写方法,但不能使用包含更多参数的方法来重写方法,即使它们都是可选的(这会导致导致调用与重写的方法具有相同的签名),例如您有:

    class bassClass{ public virtual void someMethod()}
    class subClass :bassClass{ public override void someMethod()} //合法
    //以下内容是非法的,尽管它会被称为 someMethod();
    //类子类:bassClass{公共覆盖无效someMethod(int可选= 5)} 
    
  2. 您可以使用另一个不带参数的方法重载具有默认参数的方法,(这会产生灾难性的影响,因为我稍后会讨论),所以下面的代码是合法的:

    void myfunc(int optional = 5){ /* 这里有一些代码*/ } //带默认值的函数
    void myfunc(){ /* 这里有一些代码*/ } //无参数
    myfunc(); //调用哪个?,没有参数的那个!
    
  3. 当使用反射时必须始终提供默认值。

所有这些都足以证明.Net采取了第二次实现,因此OP看到的行为是正确的,至少根据.Net而言。

.Net 方法的问题

然而,.Net 方法确实存在问题。

  1. 一致性

    • 就像OP的问题一样,当覆盖继承方法中的默认值时,结果可能是不可预测的

    • 当默认值的原始植入发生更改时,并且由于调用者不必重新编译,我们最终可能会得到不再有效的默认值

    • 反射要求您提供默认值,调用者不必知道该值
  2. 破坏代码

    • 当我们有一个带有默认参数的函数并且后来添加一个没有参数的函数时,所有调用现在都将路由到新函数,从而破坏所有现有代码,而不会发出任何通知或警告!

    • 类似的情况也会发生,如果我们稍后去掉不带参数的函数,那么所有调用都会自动路由到带有默认参数的函数,同样不会有任何通知或警告!尽管这可能不是程序员的本意

    • 此外,它不一定是常规实例方法,扩展方法也会出现同样的问题,因为不带参数的扩展方法将优先于带默认参数的实例方法!

总结:远离可选参数,并使用替代重载(正如 .NET 框架本身所做的那样)

Either Way It Needs A Fix

I would definitely regard it as a bug, either because the results is wrong or if the results are expected then the compiler should not let you declare it as "override", or at least provide a warning.

I would recommend you to report this to Microsoft.Connect

But Is It Right Or Wrong?

However regarding whether this is the expected behavior or not, let us first analyze the two views on it.

consider we have the following code:

void myfunc(int optional = 5){ /* Some code here*/ } //Function implementation
myfunc(); //Call using the default arguments

There are two ways to implement it:

  1. That optional arguments are treated like overloaded functions, resulting in the following:

    void myfunc(int optional){ /* Some code here*/ } //Function implementation
    void myfunc(){ myfunc(5); } //Default arguments implementation
    myfunc(); //Call using the default arguments
    
  2. That the default value is embedded in the caller, thus resulting in the following code:

    void myfunc(int optional){ /* Some code here*/ } //Function implementation
    myfunc(5); //Call and embed default arguments
    

There are many differences between the two approaches, but we will first take a look on how the .Net framework interprets it.

  1. In .Net you can only override a method with a method that contains the same number of arguments, but you cannot override with a method containing more arguments, even if they are all optional (which would result in a call haveing the same signature as the overridden method), say for example you have:

    class bassClass{ public virtual void someMethod()}
    class subClass :bassClass{ public override void someMethod()} //Legal
    //The following is illegal, although it would be called as someMethod();
    //class subClass:bassClass{ public override void someMethod(int optional = 5)} 
    
  2. You can overload a method with default arguments with another method with no arguments, (this has disastrous implications as I will discuss in a moments), so the folloing code is legal:

    void myfunc(int optional = 5){ /* Some code here*/ } //Function with default
    void myfunc(){ /* Some code here*/ } //No arguments
    myfunc(); //Call which one?, the one with no arguments!
    
  3. when using reflection one must always provide a default value.

All of which are enough to prove that .Net took the second implementation, so the behavior that the OP saw is right, at least according to .Net.

Problems With the .Net Approach

However there are real problems with the .Net approach.

  1. Consistency

    • As in the OP's problem when overriding the default value in an inherited method, then results might be unpredictable

    • When the original implantation of the default value is changed, and since the callers don't have to get recompiled, we might end up with default values that are no longer valid

    • Reflection requires you to provide the default value, which the caller doesn't have to know
  2. Breaking code

    • When we have a function with default arguments and latter we add a function with no arguments, all calls will now route to the new function, thus breaking all existing code, without any notification or warning!

    • Similar will happen, if we later take away the function with no arguments, then all calls will automatically route to the function with the default arguments, again with no notification or warning! although this might not be the intention of the programmer

    • Furthermore it does not have to be regular instance method, an extension method will do the same problems, since an extension method with no parameters will take precedence over an instance method with default parameters!

Summary: STAY AWAY FROM OPTIONAL ARGUMENTS, AND USE INSTEAD OVERLOADS (AS THE .NET FRAMEWORK ITSELF DOES)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文