Why does it work? Method overload + method override + polymorphism

In the following code:

public abstract class MyClass { public abstract bool MyMethod( Database database, AssetDetails asset, ref string errorMessage); } public sealed class MySubClass : MyClass { public override bool MyMethod( Database database, AssetDetails asset, ref string errorMessage) { return MyMethod(database, asset, ref errorMessage); } public bool MyMethod( Database database, AssetBase asset, ref string errorMessage) { // work is done here } } 

where AssetDetails is a subclass of AssetBase.

Why does the first MyMethod call the second at runtime when it passed AssetDetails instead of getting stuck in an infinite recursion loop?

+12
override polymorphism c # overloading
Dec 02 '09 at 14:25
source share
4 answers

C # will allow your call to another implementation, because method calls to an object where the class for this object has its own implementation will be preferable to overridden or inherited.

This can lead to subtle and inaccessible problems, such as those shown here.

For example, try this code (read it first, then compile and execute it), see if it does what you expect from it.

 using System; namespace ConsoleApplication9 { public class Base { public virtual void Test(String s) { Console.Out.WriteLine("Base.Test(String=" + s + ")"); } } public class Descendant : Base { public override void Test(String s) { Console.Out.WriteLine("Descendant.Test(String=" + s + ")"); } public void Test(Object s) { Console.Out.WriteLine("Descendant.Test(Object=" + s + ")"); } } class Program { static void Main(string[] args) { Descendant d = new Descendant(); d.Test("Test"); Console.In.ReadLine(); } } } 

Note: if you specify a variable of type Base instead of Descendant , the call will switch to another method, try changing this line:

 Descendant d = new Descendant(); 

and run again:

 Base d = new Descendant(); 

So, what could you call Descendant.Test(String) then?

My first attempt looks like this:

 public void Test(Object s) { Console.Out.WriteLine("Descendant.Test(Object=" + s + ")"); Test((String)s); } 

This did not help me, and instead just called Test(Object) over and over for the possible.

But the following work. Since when we declare the variable d Base , we end up calling the correct virtual method, we can also resort to this trick:

 public void Test(Object s) { Console.Out.WriteLine("Descendant.Test(Object=" + s + ")"); Base b = this; b.Test((String)s); } 

This will print:

 Descendant.Test(Object=Test) Descendant.Test(String=Test) 

you can also do this from outside:

 Descendant d = new Descendant(); d.Test("Test"); Base b = d; b.Test("Test"); Console.In.ReadLine(); 

prints the same thing.

But first you need to know about the problem , which is completely different.

+10
Dec 02 '09 at 14:29
source share

See C # Language Specification on Member Search and Overload Resolution . The override method of the derived class is not a candidate due to Member search rules, and the base class method is not the best match based on overload resolution rules.

Section 7.3

First, a set of all available (section 3.5) elements is created with the name N declared in T and the base types (section 7.3.1) T. Declarations containing an override modifier are excluded from the set. If no member named N exists and is accessible, then the search will not match and the next steps will not be evaluated.

Section 7.4.2:

Each of these contexts defines a set of candidate elements and a list of arguments in its own unique way, as described in detail in the sections listed above. For example, a set of candidates for calling a method does not include overridden methods (section 7.3), and methods in the base class are not candidates if any method in the derived class is applicable (section 7.5.5.1). (my accent)

+5
Dec 02 '09 at 14:34
source share

As others correctly pointed out, when a choice is given between two applicable candidate methods in a class, the compiler always selects the one that was initially declared "closer" to the class that contains the call site when exploring the hierarchy of the base class.

This seems controversial. Of course, if an exact match is specified in the base class, this is a better match than the inaccurate match declared in the derived class, right?

No. There are two reasons to choose a more derivative method always by a less derivative method.

First, the author of the derived class has much more information than the author of the base class. The author of the derived class knows everything about the base class and the derived class, which, after all, is the class that the caller actually uses. When choosing between a method call written by someone who knows everything except someone who knows only something about the type that the caller uses, it makes sense to prioritize the method call written by the developer of the derived class.

Secondly, this choice leads to a failure form of a fragile base class. We want to protect you from this failure and therefore have written rules for resolving congestion to avoid it when possible.

For a detailed explanation of how this rule protects you from failures of a fragile base class, see my article on this topic:

http://blogs.msdn.com/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

And for articles about other ways that languages ​​interact with situations with a fragile base class, see:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

+4
Dec 02 '09 at 16:01
source share

Because this is how language is defined. For virtual members, an implementation that is called at runtime when the method exists in both the base class and the derived class is based on the specific type of object to which the method is called, and not the declared type of the variable that contains the reference to the object. Your first MyMethod is in an abstract class. Thus, it can never be called from an object of type MyClass - because such an object can never exist. All you can set is a derived class of MySubClass . The specific type is MySubClass , so the implementation is called, regardless of the fact that the code that calls it is in the base class.

For non-virtual participants / methods, the opposite is true.

+1
Dec 02 '09 at 14:31
source share



All Articles