Greg Young [MVP]

Sponsors

The Lounge

News

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Overloaded Deception

A very interesting bit of code caught my eye yesterday courtesy of Peter Ritchie (example clarified by Jon Skeet) in one of the mailing lists I am subscribed to, it’s a real gem.  

Without running it … what does this code do and why?

using System;

 

class Base {

    public virtual void Foo(string x) {

        Console.WriteLine("Base.Foo(string");

    }

}

 

class Derived : Base {

    public override void Foo(string x) {

        Console.WriteLine("Derived.Foo(string)");

    }

 

    public virtual void Foo(object o) {

        Console.WriteLine("Derived.Foo(object)");

    }

}

 

class Test {

    static void Main() {

        new Derived().Foo("this is a string");

    }

}

 

 A) it prints Derived.Foo(string) because it is the best overload found
 B) it prints Derived.Foo(object) because we never typed the string passed in and assumes it to be an object

 C) it prints Derived.Foo(object) because it ignores the string overload
 D) it prints Derived.Foo(object) because the CLR is broken
 E) it prints Base.Foo(string)

Space deliberately left so you have to scroll …

 

 

 

 

 

 

 

 

 

C) It prints Derived.Foo(object). Intuitive eh? I think this might just be added to my list of interview questions. The overridden member is not considered when resolving the overload on the derived class as it is defined on the base.

I had to break out my copy of the C# specification to verify this is correct but it is.  

This is not something that is controlled by the CLR as we can see by looking at the IL in question

.method private hidebysig static void Main() cil managed
{
      .entrypoint
      .maxstack 8
      L_0000: nop
      L_0001: newobj instance void Derived::.ctor()
      L_0006: ldstr "this is a string"
      L_000b: callvirt instance void Derived::Foo(object)
      L_0010: nop
      L_0011: ret
}

 

VB.NET will not emit the same IL given the following code ..

MustInherit Class base

    Public MustOverride Sub Foo(ByVal s As String)

End Class

 

Class derived

    Inherits base

    Public Overloads Sub Foo(ByVal o As Object)

        Console.WriteLine("Derived:Foo object")

    End Sub

    Public Overrides Sub Foo(ByVal s As String)

        Console.WriteLine("Derived:Foo string")

    End Sub

End Class

 

 

Module Module1

    Sub Main()

        Dim d As New derived()

        d.Foo("test")

    End Sub

End Module

 

Prints Derived:Foo string

I wonder how many of the code converters will pick this up? J Not one I have tested so far, probably because there is no good way to convert this code (you could feasably go through and CType it to an object but you need some pretty in depth information about the types to do it) … This is also something to watch out for when dealing with CodeDOM as the same CodeDOM tree will express differring behaviors.

 


Posted Sat, Jul 1 2006 2:34 AM by Greg
Filed under:

[Advertisement]

Comments

johnwood wrote re: Overloaded Deception
on Sat, Jul 1 2006 9:48 PM
Two points:
1. I would consider this a bug in the compiler, simply because the fact it's ignoring that the base class has a string overload makes it counter-intuitive. There's nothing clever about it - it's a bug, they should fix it.
2. That would make a terrible interview question unless you were looking for someone with experience in testing compilers.
Greg wrote re: Overloaded Deception
on Sun, Jul 2 2006 12:21 AM
john:

its compliant with the C# spec

"First, the set of all accessible (§10.5) members named N declared in T
and the base types (§14.3.1) of T is constructed. Declarations that
include an override modifier are excluded from the set."


It being that it is compliant with the specification it cannot be called a compiler bug. I agree that it is non-intuitive and should be changed in the spec, should we fault the C# team for following the spec? Their goal is to provide a compiler that is compliant with ECMA 334.
johnwood wrote re: Overloaded Deception
on Sun, Jul 2 2006 9:53 AM
You're right, perhaps faulting the compiler team isn't appropriate. Given it's written so explicitly in the spec there was obviously intention behind this behavior, so I imagine there is some explanation or justification in their minds. Would be interesting to know what it was. It's just the kind of issue that would have you sitting there scratching your head for a few hours trying to figure out what the hell is going on. The whole point of inheritance is to make the inherited behavior as transparent as possible, and this seems to go completely against that tenet of OOP IMO.
Peter Ritchie wrote re: Overloaded Deception
on Sun, Jul 2 2006 11:24 AM
For anyone interested, this originated from the following MSDN Forums post:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=515521&SiteID=1
TrackBack wrote http://geekswithblogs.net/afeng/archive/2006/07/01/83833.aspx
on Sun, Jul 2 2006 2:40 PM
BirgerH wrote re: Overloaded Deception
on Mon, Jul 3 2006 1:30 AM
I'm sure it's not a bug.

Anders Heijlsberg seems to be well aware that "the way we do overload resolution in C# is different from any other language I know of, for reasons of versioning." http://www.artima.com/intv/nonvirtualP.html

He doesn't explain a lot but I suppose it makes sense in some situations:

Imagine version 1 of your example only had Derived.Foo(object) and Base.Foo(string) was introduced in version 2. The C# code would always call the same method but the VB.NET code wouldn't.
johnwood wrote re: Overloaded Deception
on Mon, Jul 3 2006 7:32 PM
There are fundamental problems with versioning in OOP - like the fragile base class problem for example. People expect those type of versioning issues when using OOP I think. IMO it's rarely actually an issue. Making such a fundamental change to the language just to work around one particular issue with versioning in OOP is just going to confuse people. I think it was a bad decision, but one that's obviously too late to change.
Greg wrote re: Overloaded Deception
on Tue, Jul 4 2006 12:26 PM
Could you not offer the best of both worlds?

If the method is on the base it is not considerred for overload resolution. If the method is overriden in the derived class it is.
johnwood wrote re: Overloaded Deception
on Tue, Jul 4 2006 12:57 PM
You know I think that would have been better. I can't think of any reason off the top of my head why that wouldn't work. Shame it's too late.
Jeffrey Palermo wrote re: Overloaded Deception
on Wed, Jul 12 2006 6:46 PM
I hope you were kidding about adding it to interview questions.  If a candidate misses the answer, all you know is they they haven't see this post or the MSDN thread addressing it.  
Greg wrote re: Overloaded Deception
on Thu, Jul 13 2006 12:30 AM
that is right Jeffrey! though they would get bonus points for either :)
johnwood wrote re: Overloaded Deception
on Thu, Jul 13 2006 12:40 AM
It is quite funny, though, how some interviewers seem obsessed with questioning their victims on some wildly obscure fact they learnt within the past few days. The times I've done interviews with someone and they've started with something like "So exactly what permissions are disallowed in .Net when running in a sandbox?". Of course sometimes it's actually quite interesting and revealing to see how people go about answering a question they have no clue about :)
barrkel wrote re: Overloaded Deception
on Wed, Aug 2 2006 5:19 PM
I'm not sure that the specification text quoted implies the given behaviour.

"First, the set of all accessible (§10.5) members named N declared in T
and the base types (§14.3.1) of T is constructed. Declarations that
include an override modifier are excluded from the set."

By removing declarations that include an override modifier, it simply removes false "overload ambiguities" created by the overrides. The original base method, declared virtual, won't be excluded from the set (since it doesn't include an override modifier), and thus should be eligible for overload resolution by way of argument type matching.
barrkel wrote re: Overloaded Deception
on Wed, Aug 2 2006 5:37 PM
After further looking, I believe that section 14.5.5.1 (ECMA 334 4th ed) is where this behaviour is defined. Paraphrasing, the two important steps are:

1) Find out the applicable methods first (i.e. the ones that could possibly be called). This can permit overloading between base and derived when the two types aren't compatible with one another, such as when the derived class's method takes an 'int' and the base class's method takes a 'string'.

2) Next, narrow down the applicable methods by removing those not in the most derived type for each method:

"The set of candidate methods is reduced to contain only methods from the most derived types: For each method C.F in the set, where C is the type in which the method F is declared, all methods declared in a base type of C are removed from the set."

Add a Comment

(required)  
(optional)
(required)  
Remember Me?