Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this polymorphic C# code print what it does?

I was recently given the following piece of code as a sort-of puzzle to help understand Polymorphism and Inheritance in OOP - C#.

// No compiling!
public class A
{
     public virtual string GetName()
     {
          return "A";
     }
 }

 public class B:A
 {
     public override string GetName()
     {
         return "B";
     }
 }

 public class C:B
 {
     public new string GetName()
     {
         return "C";
     }
 }

 void Main()
 {
     A instance = new C();
     Console.WriteLine(instance.GetName());
 }
 // No compiling!

Now, after a long, long chat with the other developer who presented the puzzle, I know what the output is, but I won't spoil it for you. The only issue I'm really having is how we get to that output, how the code steps through, what's inheriting what, etc.

I thought C would be returned as that seems to be the class that is defined. Then I went through my head as to whether B would be returned because C inherits B - but B also inherits A (which is where I got confused!).


Question:

Could anyone explain how polymorphism and inheritance play their part in retrieving the output, eventually displayed on screen?

like image 782
Daniel May Avatar asked Oct 02 '09 08:10

Daniel May


People also ask

Does C allow polymorphism?

In computer science, polymorphism is a programming language feature that allows values of different data types to be handled using a uniform interface. According to that definition, no, C doesn't natively support polymorphism.

What is polymorphism in C?

Polymorphism example in C++ Polymorphism is a key feature of object oriented programming that means having multiple forms. This is divided into compile time polymorphism and runtime polymorphism in C++. An example of compile time polymorphism is function overloading or operator overloading.

What does polymorphic mean C++?

Polymorphism in C++ means, the same entity (function or object) behaves differently in different scenarios. Consider this example: The “ +” operator in c++ can perform two specific functions at two different scenarios i.e when the “+” operator is used in numbers, it performs addition.

What makes a function polymorphic?

A function that can evaluate to or be applied to values of different types is known as a polymorphic function. A data type that can appear to be of a generalized type (e.g. a list with elements of arbitrary type) is designated polymorphic data type like the generalized type from which such specializations are made.


4 Answers

The correct way to think about this is to imagine that every class requires its objects to have a certain number of "slots"; those slots are filled with methods. The question "what method actually gets called?" requires you to figure out two things:

  1. What are the contents of each slot?
  2. Which slot is called?

Let's start by considering the slots. There are two slots. All instances of A are required to have a slot we'll call GetNameSlotA. All instances of C are required to have a slot we'll call GetNameSlotC. That's what the "new" means on the declaration in C -- it means "I want a new slot". Compared to the "override" on the declaration in B, which means "I do not want a new slot, I want to re-use GetNameSlotA".

Of course, C inherits from A, so C must also have a slot GetNameSlotA. Therefore, instances of C have two slots -- GetNameSlotA, and GetNameSlotC. Instances of A or B which are not C have one slot, GetNameSlotA.

Now, what goes into those two slots when you create a new C? There are three methods, which we'll call GetNameA, GetNameB, and GetNameC.

The declaration of A says "put GetNameA in GetNameSlotA". A is a superclass of C, so A's rule applies to C.

The declaration of B says "put GetNameB in GetNameSlotA". B is a superclass of C, so B's rule applies to instances of C. Now we have a conflict between A and B. B is the more derived type, so it wins -- B's rule overrides A's rule. Hence the word "override" in the declaration.

The declaration of C says "put GetNameC in GetNameSlotC".

Therefore, your new C will have two slots. GetNameSlotA will contain GetNameB and GetNameSlotC will contain GetNameC.

We've now determined what methods are in what slots, so we've answered our first question.

Now we have to answer the second question. What slot is called?

Think about it like you're the compiler. You have a variable. All you know about it is that it is of type A. You're asked to resolve a method call on that variable. You look at the slots available on an A, and the only slot you can find that matches is GetNameSlotA. You don't know about GetNameSlotC, because you only have a variable of type A; why would you look for slots that only apply to C?

Therefore this is a call to whatever is in GetNameSlotA. We've already determined that at runtime, GetNameB will be in that slot. Therefore, this is a call to GetNameB.

The key takeaway here is that in C# overload resolution chooses a slot and generates a call to whatever happens to be in that slot.

like image 123
Eric Lippert Avatar answered Oct 17 '22 23:10

Eric Lippert


It should return "B" because B.GetName() is held in the little virtual table box for the A.GetName() function. C.GetName() is a compile time "override", it doesn't override the virtual table so you can't retrieve it through a pointer to A.

like image 25
Blindy Avatar answered Oct 18 '22 01:10

Blindy


Easy, you only have to keep the inheritance tree in mind.

In your code, you hold a reference to a class of type 'A', which is instantiated by an instance of type 'C'. Now, to resolve the exact method address for the virtual 'GetName()' method, the compiler goes up the inheritance hierarchy and looks for the most recent override (note that only 'virtual' is an override, 'new' is something completely different...).

That's in short what happens. The new keyword from type 'C' would only play a role if you would call it on an instance of type 'C' and the compiler then would negate all possible inheritance relations altogether. Strictly spoken, this has nothing to do at all with polymorphism - you can see that from the fact that whether you mask a virtual or non-virtual method with the 'new' keyword doesn't make any difference...

'New' in class 'C' means exactly that: If you call 'GetName()' on an instance of this (exact) type, then forget everything and use THIS method. 'Virtual' in contrary means: Go up the inheritance tree until you find a method with this name, no matter what the exact type of the calling instance is.

like image 3
Thomas Weller Avatar answered Oct 18 '22 01:10

Thomas Weller


OK, the post is a bit old, but it's an excellent question and an excellent answer, so I just wanted to add my thoughts.

Consider the following example, which is the same as before, except for the main function:

// No compiling!
public class A
{
    public virtual string GetName()
    {
        return "A";
    }
}

public class B:A
{
    public override string GetName()
    {
        return "B";
    }
}

public class C:B
{
    public new string GetName()
    {
        return "C";
    }
}

void Main()
{
    Console.Write ( "Type a or c: " );
    string input = Console.ReadLine();

    A instance = null;
    if      ( input == "a" )   instance = new A();
    else if ( input == "c" )   instance = new C();

   Console.WriteLine( instance.GetName() );
}
// No compiling!

Now it's really obvious that the function call cannot be bound to a specific function at compile time. Something must be compiled however, and that information can only depend on the type of the reference. So, it would be impossible to execute the GetName function of class C with any reference other than one of type C.

P.S. Maybe I should've used the term method in stead of function, but as Shakespeare said: A function by any other name is still a function :)

like image 2
Mange Avatar answered Oct 18 '22 00:10

Mange