C # advanced options for overridden methods

It seems that the .NET Framework has a problem with optional parameters when overriding a method. The code output is below: “BBB” “Ahh,” But the expected result: “BBB” “BBB.” Is there a solution for this. I know that this can be solved with method overloading, but I wonder why this is necessary. Also, the code works great 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(); } } 
+67
override c # optional-parameters
Jan 18 '12 at 12:05
source share
9 answers

It is worth noting here that an overridden version is called every time. Change the override to:

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

And the result:

 derived: bbb derived: aaa 

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

  • It defines an interface for calling another code.
  • It defines the implementation that is executed upon invocation.

It may not do both, since the abstract method does only the first.

Inside the BBB call to MyMethod() calls the method defined in AAA .

Since there is an override in the BBB , a call to this method results in a call to the BBB .

Now the definition in AAA tells you the call code for two things (well, some others don't matter here either).

  • Signature void MyMethod(string) .
  • (For those languages ​​that support it), the default value for one parameter is "aaa" , and therefore when compiling the code of the form MyMethod() , if the method corresponding to MyMethod() is not found, you can replace it with `MyMethod ( "aaa").

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

Inside the BBB , AAA is considered the place where the AAA methods are defined, even if they are overridden in the BBB so that they can be overloaded.

At run time, MyMethod(string) is called with the argument "aaa". Since there is an overridden form called a form, but it is not called with "bbb" because this value has nothing to do with the runtime implementation, but with the definition of compilation time.

Adding this. changes whose definition is checked, and therefore changes which argument is used in the call.

Edit: why it seems to me more intuitive.

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

If I were BBB encoding, then if I would call or redefine MyMethod(string) , I would think of it as "doing AAA stuff" - it BBB take "do AAA stuff", but it's all doing AAA . Therefore, whether it be a call or an override, I will know that it was the AAA that defined MyMethod(string) .

If I were to call code that used BBB , I would think about “using BBB stuff”. Perhaps I do not know very well what was originally defined in AAA , and I would think of it as simplicity of implementation (if I did not use the AAA interface as well).

The behavior of the compiler is consistent with my intuition, so when I first read the question, it seemed to me that Mono had an error. After reviewing, I don’t see how either the specified behavior performs better than the other.

Nevertheless, despite the fact that, remaining on a personal level, I will never use additional parameters with abstract, virtual or overridden methods, and if I redefine someone else, I would compare them.
+21
Jan 18 2018-12-18T00:
source share

You can resolve the ambiguity by calling:

 this.MyMethod(); 

(in MyMethod2() )

Is this a mistake difficult? however, this does not look consistent. Resharper warns that you simply do not have changes to the default value in the override, if that helps; p Of course, resharper also tells you that this. is redundant and offers to remove it for you ... which changes the behavior - therefore resharper is also not ideal.

It looks like it might qualify as a compiler error, I will give it to you. I would have to look really carefully to make sure ... where is Eric, when do you need him, huh ??




Edit:

The key here is the language specification; look at §7.5.3:

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

(and, indeed, §7.4 does not explicitly take into account override methods)

There is some kind of conflict ... he claims that the base methods are not used if there is an applicable method in the derived class that will lead us to the derived method, but at the same time he says that methods marked with override not considered.

But clause 7.5.1.1 says:

For virtual methods and indexers defined in classes, the list of parameters is selected from the most specific declaration or redefinition of a function member, starting with the static type of the recipient and looking at its base classes.

and then §7.5.1.2 explains how values ​​are evaluated during a call:

During a call to a member function (§7.5.4), expressions or references to variables in the argument list are evaluated in order from left to right as follows:

... (notch) ...

When arguments are omitted from a function member with the corresponding optional parameters, the default arguments to the function element declaration are implicitly passed. Since they are always constant, their evaluation will not affect the evaluation of the remaining arguments.

This clearly emphasizes that he is considering a list of arguments that was previously defined in clause 7.5.1.1, coming from the most specific declaration or override . It seems reasonable that this is a "method declaration", referred to in clause 7.5.1.2, so the value passed must be from the most received to the static type.

This would mean: csc has an error, and it should use a derivative version ("bbb bbb"), unless it is limited (via base. Or casting to the base type) in order to see the declarations of the base method (§7.6.8).

+33
Jan 18 '12 at 12:14
source share

It looks like a mistake to me. I believe that this is well indicated, and that it should behave as if you were calling a method with an explicit this prefix.

I simplified this example to use only one virtual method, and show which implementation is being 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 is three calls to RunTests. The important bits of the specification for the first two calls are sections 7.5.1.1, which talk about the list of parameters that will be used when searching for the corresponding parameters:

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

And section 7.5.1.2:

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

“Corresponding optional parameter” is a bit that binds 7.5.2 - 7.5.1.1.

For both M() and this.M() this list of parameters should be the same in Derived as the static receiver type of Derived . Indeed, you can say that the compiler treats it as a list of parameters earlier in compilation, as if you make the parameter mandatory in Derived.M() , both calls fail - so calling M() 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 a parameter in Derived , but make it mandatory in Base , the M() call ends up using null as the argument value. If nothing else, I would say that this indicates an error: a null value cannot come from anywhere. (It is null due to the fact that this is a default value of type string ; it always just uses the default value for a parameter type.)

Section 7.6.8 spec deals with the base .M (), which states that as well as non-virtual behavior, the expression is treated as ((Base) this).M() ; therefore, it is fully corrected for the base method to determine an effective list of parameters. This means the final line is correct.

Just to make it easier for everyone who wants to see a really strange error, 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(); } } 
+15
Jan 19 '12 at 7:26
source share

You tried:

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

So, you are actually telling your program to use the overriden method.

+10
Jan 18 '12 at 12:12
source share

The behavior is certainly very strange; I don’t understand if this is really a compiler error, but it could be.

There is a lot of snow on campus today, and Seattle is not doing very well with snow. My bus doesn’t work today, so I won’t be able to get into the office to compare what C # 4, C # 5 and Roslyn say about this case, and if they do not agree. I will try to publish the analysis later this week when I get back to the office and can use the proper debugging tools.

+9
Jan 18 2018-12-18T00:
source share

Perhaps this is due to ambiguity, and the compiler gives priority to the base / superclass. The following is a change in the code for your BBB class with the addition of a link to the this , it displays 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 he implies is to always use the this whenever you call properties or methods in the current instance of the class as best practice .

I would be concerned if this ambiguity in the base and child method didn’t even raise a compiler warning (if it weren’t for an error), but if that happens, then it was invisible, I suppose.

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

EDIT: Consider the following exemplary 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: additional parameter values ​​- compilation time There is one thing and one thing that you need to remember only when using optional parameters. If you keep this in mind, you may well understand and avoid any potential pitfalls with using them: This is one thing: the additional parameters are compilation time, syntactic sugar!

Pitfall: Beware of default options in inheritance and interface implementation

Now the second potential problem is inheritance and implementation of the interface. Ill 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's happening? Well, although the object in each case is SubTag, whose tag is "SubTag", you will get:

1: SubTag 2: BaseTag 3: ITag

But be sure to make sure that you:

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

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

+5
Jan 18 '12 at 12:17
source share

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

 public override void MyMethod2() { this.MyMethod("aaa"); } 
+1
Jan 18 '12 at 12:44
source share

Any way to fix it

I would definitely see this as an error because the results were incorrect or the results were expected, so the compiler should not allow you to declare it as "override" or at least provide a warning.

I would recommend that you let Microsoft.Connect know about it.

But is this right or wrong?

However, as to whether this is the expected behavior or not, let's first analyze the two views on it.

We believe that 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:

  • These optional arguments are treated as 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 
  • The default value is embedded in the caller, 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 look at how the .Net framework interprets it.

  • In .Net, you can only override a method using a method that contains the same number of arguments, but you cannot override a method that contains more arguments, even if all of them are optional (which will result in a call having 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)} 
  • You can overload the method with the default arguments by another method without arguments (this has disastrous consequences, as I will discuss at some points), so the following 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! 
  • when using reflection, you must always specify a default value.

All of them are enough to prove that .Net took the second implementation, so the behavior that the OP sees is correct, at least according to .Net.

Problems with the .Net approach

However, there are real problems with the .Net approach.

  • Coherence

    • As with the OP problem, when overriding the default value in the inherited method, the results can be unpredictable

    • When the initial implantation of the default value changes, and since the callers do not need to recompile, we can get the default values ​​that are no longer valid.

    • Reflection requires that you provide a default value that the caller should not know
  • Code break

    • When we have a function with default arguments and the last one, we add a function without arguments, all calls will now be redirected to the new function, thereby violating all existing code without notice or warning!

    • It will be similar if later we remove the function with no arguments, then all calls will automatically go to the function with the default arguments, again without notice or warning! although this may not be the intention of the programmer

    • In addition, it should not be a regular instance method, the extension method will perform the same problems, since an extension method without parameters will take precedence over an instance method with default parameters!

Summary: STAY AWAY FROM ADDITIONAL ARGUMENTS AND USE OVERLOADS TOGETHER (AS THE STANDARD FEATURES ARE)

0
Aug 10 '12 at 18:46
source share

Agree generally with @Marc Gravell.

However, I would like to mention that the problem is quite old in the C ++ world ( http://www.devx.com/tips/Tip/12737 ), and the answer looks "Unlike virtual functions that are allowed at runtime, the arguments by default are allowed statically, that is, at the time of compilation. " Thus, this C # compiler behavior was rather consciously taken because of the sequence, despite its unexpectedness, it seems.

0
Oct 18 '12 at 8:38
source share



All Articles