How method hiding works in C#? (Part Two)
Asked Answered
S

4

13

The following program prints

A:C(A,B)
B:C(A,B)

(as it should)

public interface I
{
    string A();
}

public class C : I
{
    public string A()
    {
        return "A";
    }

    public string B()
    {
        return "B";
    }
}

public class A
{
    public virtual void Print(C c)
    {
        Console.WriteLine("A:C(" + c.A() + "," + c.B() + ")");
    }
}

public class B : A
{
    public new void Print(C c)
    {
        Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
    }

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }
}

class Program
{
    public static void Main(string[] args)
    {
        A a = new A();
        B b = new B();
        C c = new C();
        a.Print(c);
        b.Print(c);
    }
}

however, if I change keyword 'new' to 'override' in class B like so:

    public override void Print(C c)

all of a sudden program starts to print:

A:C(A,B)
B:I(A)

Why?

Strickle answered 2/4, 2009 at 16:18 Comment(0)
E
8

This is to do with how overloaded methods are resolved.

Effectively (simplified somewhat), the compiler first looks at the declared type of the expression (B) in this case and looks for candidate methods which are first declared in that type. If there are any methods which are appropriate (i.e. where all the arguments can be converted to the method's parameter types) then it doesn't look at any parent types. This means that overridden methods, where the initial declaration is in a parent type, don't get a look-in if there are any "freshly declared" appropriate methods in the derived type.

Here's a slightly simpler example:

using System;

class Base
{
    public virtual void Foo(int x)
    {
        Console.WriteLine("Base.Foo(int)");
    }
}

class Derived : Base
{
    public override void Foo(int x)
    {
        Console.WriteLine("Derived.Foo(int)");
    }

    public void Foo(double d)
    {
        Console.WriteLine("Derived.Foo(double)");
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.Foo(10);
    }
}

This prints Derived.Foo(double) - even though the compiler knows there is a matching method with a parameter of type int, and the argument is type int, and the conversion from int to int is "better" than the conversion from int to double, the fact that only the Foo(double) method is originally declared in Derived means the compiler ignores Foo(int).

This is highly surprising IMO. I can see why it would be the case if Derived didn't override Foo - otherwise introducing a new, more specific, method in the base class could change the behaviour unexpectedly - but clearly Derived here knows about Base.Foo(int) as it's overriding it. This is one of the (relatively few) points where I believe the C# designers made the wrong decision.

Econometrics answered 2/4, 2009 at 16:27 Comment(1)
Furthermore, if you passed d as a parameter to a method which expects Base, you would get Derived.Foo(int) (same as writing (d as Base).Foo(10); inside Main()). Although it is clear how this works, I find this pretty smelly and therefore avoid method hiding in all cases.Fpc
L
1

Ok, so

    public new void Print(C c)
    {
        Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
    }

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }

This declares a new method for the print. Now because B inherits from A, you are simly calling the new method twice. When you overide the method, this then changes the method signature when you call for A, but when you call the B signature, then it has its own method signature.

I am not sure if I am explaining clear but good question.

using new:

A and B get the same implementation of the Print Method.

using override:

A has a different method signature to B as, you have not changed the method signature in B only in A.

using the new it basically ignores this:

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }
Lorylose answered 2/4, 2009 at 16:26 Comment(0)
B
1

This was a great question.
All the answers can be found here: http://msdn.microsoft.com/en-us/library/6fawty39(VS.80).aspx

The gist of it is this:

...the C# compiler will first try to make the call compatible with the versions of [functionName] declared originally on [the derived class]. Override methods are not considered as declared on a class, they are new implementations of a method declared on a base class. Only if the C# compiler cannot match the method call to an original method on [the Derived class] will it try to match the call to an overridden method with the same name and compatible parameters.

So because you have a new method Print(I i) on the derived class which matches the argument "c", (because c implements I), that method takes precedence over the "override" method.

When you mark the method as "new", they are both considered to be implemented on the derived class, and the Print(C c) method more closely matches the parameter "c", so it takes precedence.

Borges answered 2/4, 2009 at 16:36 Comment(0)
G
0

This is at least as much a question about how method overloading works in C#. I think you've highlighted an interesting situation here...

In the first case (using the new keyword on the method), the compiler decides to use the Print method overload with the parameter of type C because it's type is exactly equivalent to that of the parameter passed (i.e. no implicit conversion is required) whereas an implicit conversion to the interface I would be required if the compiler were to choose the Print method that takes an argument of type I - in other words, it chooses the more "obvious" method overload.

In the second case (using the override keyword on the method), the compiler decides to use the overload of Print with parameter of type I because although you are overriding the Print(C c) method overload in the class B, it is effectively defined in the parent class A, making the Print(I i) method overload in fact the highest-level overload and therefore the most direct one, i.e. the first one the compiler finds.

Hopefully this will help you to understand. Let me know if I need to clairfy any points further...

Note: If I'm wrong about saying that the compiler does these things, then please correct me, though it makes little difference for the sake of the argument whether it's the compiler or CLR/JIT, it would seem.

Glorification answered 2/4, 2009 at 16:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.