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.
似乎在。net框架中,当你重写这个方法时,有一个可选参数的问题。下面代码的输出是:“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();
}
}
9 个解决方案
#1
21
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:
一个类中的方法可以做以下一两个:
- It defines an interface for other code to call.
- 它为要调用的其他代码定义了一个接口。
- 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
.
在BBB中,调用MyMethod()调用AAA中定义的方法。
Because there is an override in BBB
, calling that method results in an implementation in BBB
being called.
因为在BBB中有一个覆盖,调用该方法会导致在BBB中调用一个实现。
Now, the definition in AAA
informs calling code of two things (well, a few others too that don't matter here).
现在,AAA中的定义告诉调用代码两件事(当然,还有一些其他的事情在这里并不重要)。
- The signature
void MyMethod(string)
. - 签名无效MyMethod(字符串)。
- (For those languages that support it) the default value for the single parameter is
"aaa"
and therefore when compiling code of the formMyMethod()
if no method matchingMyMethod()
can be found, you may replace it with a call to `MyMethod("aaa"). - (对于支持它的语言)单个参数的默认值是“aaa”,因此,当编译MyMethod()形式的代码时,如果找不到与MyMethod()匹配的方法,您可以用调用‘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")
.
这就是BBB中的调用所做的:编译器看到对MyMethod()的调用,没有找到方法MyMethod(),但是找到了方法MyMethod(string)。它还看到在定义它的地方有一个默认值“aaa”,因此在编译时它将其更改为对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.
在BBB中,AAA被认为是定义AAA方法的地方,即使在BBB中被重写,也可以被重写。
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.
在运行时,使用参数“aaa”调用MyMethod(string)。因为有一个覆盖的表单,这就是调用的表单,但是它不是用“bbb”来调用的,因为这个值与运行时实现无关,而是与编译时定义无关。
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 BBB
s 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)
.
如果我正在编写BBB,那么无论调用还是重写MyMethod(string),我都会将其视为“执行AAA操作”——BBB承担“执行AAA操作”,但它仍然执行AAA操作。因此,无论调用还是重写,我都将意识到定义MyMethod(string)的是AAA。
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).
如果我调用使用BBB的代码,我会想到“使用BBB的东西”。我可能不太清楚AAA中最初定义的是什么,我可能认为这只是实现细节(如果我附近没有使用AAA接口的话)。
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.
编译器的行为符合我的直觉,这就是为什么在第一次阅读这个问题时,我觉得Mono有一个bug。经过考虑,我看不出这两种行为是如何比另一种更好地完成指定的行为的。
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.
尽管如此,在保持个人水平的同时,我不会使用带有抽象、虚拟或重写方法的可选参数,如果重写了其他人的参数,我会匹配他们的。
#2
33
You can disambiguate by calling:
您可以通过调用:
this.MyMethod();
(in MyMethod2()
)
(在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.
它是否是一个bug是很棘手的;不过,这看起来确实前后矛盾。Resharper警告你不要在重写中修改默认值,如果这有帮助的话;p当然,Resharper也会告诉你这个。是多余的,并提供删除它为你…这改变了行为——所以resharper也不是完美的。
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:
这里的关键点是语言规范;让我们看看§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).
例如,一组候选方法调用不包括方法标记覆盖(§7.4),基类和方法不是候选人如果任何方法的派生类适用(§7.6.5.1)。
(and indeed §7.4 clearly omits override
methods from consideration)
(实际上§7.4显然忽略了覆盖方法考虑)
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.
这里有一些冲突....它指出,如果派生类中有一个适用的方法,则不使用基方法—这将导致我们使用派生方法,但是同时,它说不考虑标记为override的方法。
But, §7.5.1.1 then states:
但是,§7.5.1.1状态:
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:
然后§7.5.1.2解释了值是如何评估的时候调用:
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:
在运行时调用处理函数的成员(§7.5.4)的表达式或变量引用参数列表评估,从左到右,如下:
...(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.
这明确强调,看参数列表,这是以前定义的§7.5.1.1来自最具体的声明或覆盖。它似乎是合理的,这是被称为的“方法声明”§7.5.1.2,因此价值应该从最派生产品质量通过静态类型。
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).
这表明:csc有一个bug,它应该使用派生的版本(“bbb bbb”),除非它受到限制(通过base)。或铸造基类型)来观察基本方法声明(§7.6.8)。
#3
15
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:
所以我们需要担心的是运行测试中的三个调用。前两次调用规范的重要部分是第7.5.1.1节,其中讨论了查找相应参数时要使用的参数列表:
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:
和部分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.
“相应的可选参数”是连接7.5.2到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!
M()和this.M(),参数列表应该派生的静态类型的接收机是派生的,实际上,你可以告诉编译器将作为参数列表编译早些时候,如果你使参数强制Derived.M(),这两个调用失败,所以M()调用需要的参数有默认值,但是忽略了它!
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.)
实际上,情况变得更糟:如果您为派生的参数提供一个默认值,但将它设置为Base,那么调用M()最终将null作为参数值。如果没有别的,我想说这证明了它是一个错误:null值不能来自任何有效的地方。(它是null,因为它是字符串类型的默认值;它总是使用参数类型的默认值)
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.
规范的第7.6.8节处理Base .M(),它说除了非虚行为之外,表达式还被认为是(Base) this).M();因此,基本方法用于确定有效参数列表是完全正确的。这意味着最后一行是正确的。
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:
为了让任何想要看到上面描述的真正奇怪的bug的人更容易理解,这里使用的值在任何地方都没有指定:
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();
}
}
#4
10
Have you tried:
你有试过:
public override void MyMethod2()
{
this.MyMethod();
}
So you actually tell your program to use the overriden Method.
所以你实际上告诉你的程序使用overriden方法。
#5
9
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.
昨天晚上校园里下雪了,西雅图不太适合下雪。我的公共汽车今天早上不开了,所以我不能去办公室比较c# 4, c# 5和Roslyn对这个案子的看法,如果他们不同意的话。本周晚些时候,我回到办公室后将尝试发布一份分析报告,并使用适当的调试工具。
#6
5
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':
这可能是由于歧义,编译器优先于基础/超类。下面更改了类BBB的代码,并添加了对该关键字的引用,输出为“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.
它所暗示的事情之一是,当您调用类当前实例上的属性或方法时,您应该始终使用this关键字作为最佳实践。
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.
如果base和child方法中的这种模糊性甚至没有引起编译器的警告(如果没有错误的话),我可能会担心,但是如果有的话,我想这是不可见的。
==================================================================
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
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:
会发生什么呢?即使每个案例中的对象是子标签,它的标签是" SubTag ",你会得到:
1: SubTag 2: BaseTag 3: ITag
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.
不要在现有的默认参数集中插入新的默认参数,这可能会导致不可预测的行为,不一定会抛出语法错误——添加到列表末尾或创建新方法。在继承层次结构和接口中使用默认参数时要格外小心——根据预期使用情况选择最合适的级别添加默认值。
==========================================================================
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
#7
1
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.
我认为这是因为这些默认值在编译时是固定的。如果您使用反射器,您将看到在BBB中MyMethod2的以下内容。
public override void MyMethod2()
{
this.MyMethod("aaa");
}
#8
0
Agree in general with @Marc Gravell.
同意@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.
但是,我想指出的是,这个问题在c++世界(http://www.devx.com/tips/Tip/12737)中已经足够老了,答案看起来就像“与在运行时解析的虚拟函数不同,默认参数是在编译时静态解析的。”因此,尽管c#编译器的行为出人意料,但由于一致性的原因,它还是被故意接受了。
#9
0
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:
有两种方法来实施它:
-
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
-
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.
这两种方法之间有很多不同之处,但是我们将首先看看。net框架是如何解释它的。
-
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:
在。net只能覆盖一个方法方法包含相同数量的参数,但是你不能覆盖方法包含更多参数,即使他们都是可选的(这将导致调用具有相同的签名覆盖方法),例如你说:
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)}
-
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:
您可以用另一个没有参数的方法重载带有默认参数的方法(这将带来灾难性的影响,稍后我将对此进行讨论),因此folloing代码是合法的:
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!
-
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.
所有这些都足以证明。net采用了第二种实现,因此OP所看到的行为是正确的,至少根据。net。
Problems With the .Net Approach
.Net方法的问题
However there are real problems with the .Net approach.
然而。net方法确实存在问题。
-
Consistency
一致性
-
As in the OP's problem when overriding the default value in an inherited method, then results might be unpredictable
与OP的问题一样,当在继承的方法中重写默认值时,结果可能是不可预测的
-
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
- 反射需要提供默认值,调用者不必知道这些值。
-
-
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)
摘要:不要使用可选参数,而是使用重载(正如。net框架本身所做的)
#1
21
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:
一个类中的方法可以做以下一两个:
- It defines an interface for other code to call.
- 它为要调用的其他代码定义了一个接口。
- 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
.
在BBB中,调用MyMethod()调用AAA中定义的方法。
Because there is an override in BBB
, calling that method results in an implementation in BBB
being called.
因为在BBB中有一个覆盖,调用该方法会导致在BBB中调用一个实现。
Now, the definition in AAA
informs calling code of two things (well, a few others too that don't matter here).
现在,AAA中的定义告诉调用代码两件事(当然,还有一些其他的事情在这里并不重要)。
- The signature
void MyMethod(string)
. - 签名无效MyMethod(字符串)。
- (For those languages that support it) the default value for the single parameter is
"aaa"
and therefore when compiling code of the formMyMethod()
if no method matchingMyMethod()
can be found, you may replace it with a call to `MyMethod("aaa"). - (对于支持它的语言)单个参数的默认值是“aaa”,因此,当编译MyMethod()形式的代码时,如果找不到与MyMethod()匹配的方法,您可以用调用‘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")
.
这就是BBB中的调用所做的:编译器看到对MyMethod()的调用,没有找到方法MyMethod(),但是找到了方法MyMethod(string)。它还看到在定义它的地方有一个默认值“aaa”,因此在编译时它将其更改为对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.
在BBB中,AAA被认为是定义AAA方法的地方,即使在BBB中被重写,也可以被重写。
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.
在运行时,使用参数“aaa”调用MyMethod(string)。因为有一个覆盖的表单,这就是调用的表单,但是它不是用“bbb”来调用的,因为这个值与运行时实现无关,而是与编译时定义无关。
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 BBB
s 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)
.
如果我正在编写BBB,那么无论调用还是重写MyMethod(string),我都会将其视为“执行AAA操作”——BBB承担“执行AAA操作”,但它仍然执行AAA操作。因此,无论调用还是重写,我都将意识到定义MyMethod(string)的是AAA。
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).
如果我调用使用BBB的代码,我会想到“使用BBB的东西”。我可能不太清楚AAA中最初定义的是什么,我可能认为这只是实现细节(如果我附近没有使用AAA接口的话)。
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.
编译器的行为符合我的直觉,这就是为什么在第一次阅读这个问题时,我觉得Mono有一个bug。经过考虑,我看不出这两种行为是如何比另一种更好地完成指定的行为的。
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.
尽管如此,在保持个人水平的同时,我不会使用带有抽象、虚拟或重写方法的可选参数,如果重写了其他人的参数,我会匹配他们的。
#2
33
You can disambiguate by calling:
您可以通过调用:
this.MyMethod();
(in MyMethod2()
)
(在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.
它是否是一个bug是很棘手的;不过,这看起来确实前后矛盾。Resharper警告你不要在重写中修改默认值,如果这有帮助的话;p当然,Resharper也会告诉你这个。是多余的,并提供删除它为你…这改变了行为——所以resharper也不是完美的。
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:
这里的关键点是语言规范;让我们看看§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).
例如,一组候选方法调用不包括方法标记覆盖(§7.4),基类和方法不是候选人如果任何方法的派生类适用(§7.6.5.1)。
(and indeed §7.4 clearly omits override
methods from consideration)
(实际上§7.4显然忽略了覆盖方法考虑)
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.
这里有一些冲突....它指出,如果派生类中有一个适用的方法,则不使用基方法—这将导致我们使用派生方法,但是同时,它说不考虑标记为override的方法。
But, §7.5.1.1 then states:
但是,§7.5.1.1状态:
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:
然后§7.5.1.2解释了值是如何评估的时候调用:
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:
在运行时调用处理函数的成员(§7.5.4)的表达式或变量引用参数列表评估,从左到右,如下:
...(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.
这明确强调,看参数列表,这是以前定义的§7.5.1.1来自最具体的声明或覆盖。它似乎是合理的,这是被称为的“方法声明”§7.5.1.2,因此价值应该从最派生产品质量通过静态类型。
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).
这表明:csc有一个bug,它应该使用派生的版本(“bbb bbb”),除非它受到限制(通过base)。或铸造基类型)来观察基本方法声明(§7.6.8)。
#3
15
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:
所以我们需要担心的是运行测试中的三个调用。前两次调用规范的重要部分是第7.5.1.1节,其中讨论了查找相应参数时要使用的参数列表:
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:
和部分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.
“相应的可选参数”是连接7.5.2到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!
M()和this.M(),参数列表应该派生的静态类型的接收机是派生的,实际上,你可以告诉编译器将作为参数列表编译早些时候,如果你使参数强制Derived.M(),这两个调用失败,所以M()调用需要的参数有默认值,但是忽略了它!
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.)
实际上,情况变得更糟:如果您为派生的参数提供一个默认值,但将它设置为Base,那么调用M()最终将null作为参数值。如果没有别的,我想说这证明了它是一个错误:null值不能来自任何有效的地方。(它是null,因为它是字符串类型的默认值;它总是使用参数类型的默认值)
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.
规范的第7.6.8节处理Base .M(),它说除了非虚行为之外,表达式还被认为是(Base) this).M();因此,基本方法用于确定有效参数列表是完全正确的。这意味着最后一行是正确的。
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:
为了让任何想要看到上面描述的真正奇怪的bug的人更容易理解,这里使用的值在任何地方都没有指定:
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();
}
}
#4
10
Have you tried:
你有试过:
public override void MyMethod2()
{
this.MyMethod();
}
So you actually tell your program to use the overriden Method.
所以你实际上告诉你的程序使用overriden方法。
#5
9
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.
昨天晚上校园里下雪了,西雅图不太适合下雪。我的公共汽车今天早上不开了,所以我不能去办公室比较c# 4, c# 5和Roslyn对这个案子的看法,如果他们不同意的话。本周晚些时候,我回到办公室后将尝试发布一份分析报告,并使用适当的调试工具。
#6
5
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':
这可能是由于歧义,编译器优先于基础/超类。下面更改了类BBB的代码,并添加了对该关键字的引用,输出为“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.
它所暗示的事情之一是,当您调用类当前实例上的属性或方法时,您应该始终使用this关键字作为最佳实践。
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.
如果base和child方法中的这种模糊性甚至没有引起编译器的警告(如果没有错误的话),我可能会担心,但是如果有的话,我想这是不可见的。
==================================================================
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
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:
会发生什么呢?即使每个案例中的对象是子标签,它的标签是" SubTag ",你会得到:
1: SubTag 2: BaseTag 3: ITag
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.
不要在现有的默认参数集中插入新的默认参数,这可能会导致不可预测的行为,不一定会抛出语法错误——添加到列表末尾或创建新方法。在继承层次结构和接口中使用默认参数时要格外小心——根据预期使用情况选择最合适的级别添加默认值。
==========================================================================
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
#7
1
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.
我认为这是因为这些默认值在编译时是固定的。如果您使用反射器,您将看到在BBB中MyMethod2的以下内容。
public override void MyMethod2()
{
this.MyMethod("aaa");
}
#8
0
Agree in general with @Marc Gravell.
同意@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.
但是,我想指出的是,这个问题在c++世界(http://www.devx.com/tips/Tip/12737)中已经足够老了,答案看起来就像“与在运行时解析的虚拟函数不同,默认参数是在编译时静态解析的。”因此,尽管c#编译器的行为出人意料,但由于一致性的原因,它还是被故意接受了。
#9
0
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:
有两种方法来实施它:
-
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
-
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.
这两种方法之间有很多不同之处,但是我们将首先看看。net框架是如何解释它的。
-
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:
在。net只能覆盖一个方法方法包含相同数量的参数,但是你不能覆盖方法包含更多参数,即使他们都是可选的(这将导致调用具有相同的签名覆盖方法),例如你说:
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)}
-
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:
您可以用另一个没有参数的方法重载带有默认参数的方法(这将带来灾难性的影响,稍后我将对此进行讨论),因此folloing代码是合法的:
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!
-
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.
所有这些都足以证明。net采用了第二种实现,因此OP所看到的行为是正确的,至少根据。net。
Problems With the .Net Approach
.Net方法的问题
However there are real problems with the .Net approach.
然而。net方法确实存在问题。
-
Consistency
一致性
-
As in the OP's problem when overriding the default value in an inherited method, then results might be unpredictable
与OP的问题一样,当在继承的方法中重写默认值时,结果可能是不可预测的
-
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
- 反射需要提供默认值,调用者不必知道这些值。
-
-
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)
摘要:不要使用可选参数,而是使用重载(正如。net框架本身所做的)