Let's start with the theory.
Suppose there are the following classes:

class A{} class B : A{} class C : B{} 

Next, we do UpCast :

 A a1 = new C(); 

Is the following statement true: the object a1 is an object of type C, and the base class for it is type A (that is, up the hierarchy)!?

Far from leaving the cash register, imagine what you have added to the code the following:

 class A { public virtual void Method() { Console.WriteLine("Method A invoked"); } } class B : A { public new virtual void Method() { Console.WriteLine("Method B invoked"); } } class C : B { public override void Method() { Console.WriteLine("Method C invoked"); } } 

What will be displayed on the screen?
First of all, it will seem that everything is obvious here, and we will get the output:

 Method A invoked Method A invoked Method С invoked Method C invoked 

But in fact, we get: Method A invoked Method A invoked Method A invoked Method C invoked

Based on this logic, it turns out that my previous statement is not true, and this means that all the same base class for C is B ?


We now turn to another part of the question.
For example, we have the code:

  class Program { static void Main(string[] args) { //объект типа класса А A a = new A(); //объект типа класса B B b = new B(); //UpCast, который равен объекту "b" A a1 = b; //UpCast как отдельный объект A a2 = new B(); //DownCast, который равен объекту "а1" B b1 = (B)a1; B b2 = a as B; // вернет Null, т.к. DownCast //без предварительного UpCast не возможен // B b2 = new A(); - невозможно из за безопасности типов //сравниваем b с а1,видим что типы идентичны. Console.WriteLine(b.GetType() == a1.GetType()); //сравниваем а2 с а1,видим что типы идентичны. Console.WriteLine(a2.GetType() == a1.GetType()); //сравниваем b1 и а1,видим что типы идентичны Console.WriteLine(b1.GetType() == a1.GetType()); //Проверяем сами обьекты,вернет True Console.WriteLine(a1.Equals(b)); //вернет False,но реализация этих объектов идентична Console.WriteLine(a2.Equals(a1)); //Вернет True Console.WriteLine(b1.Equals(a1)); Console.ReadKey(); } } class A { } class B : A { } 

Approximate IL code (not everything got)

So what happens behind the scenes?
As with UpCast "e \ DownCast" e , two identical objects (or rather two links pointing to the same object) have a different implementation (yes, yes - this is polymorphism). How is this achieved (that is, how does the CLR implement this model of behavior) and how does all this wonder-and-how look in the CLR itself look like? What does "inheritance" inside the CLR look like between types?

    1 answer 1

    In order:

    Is the following statement true: the object a1 is an object of type C, and the base class for it is type A (that is, up the hierarchy)!?

    Not. The correct statement is the following: the object a1 is an object of type C, and the base classes for it are types B and А

    The difference is big, because each type in the inheritance hierarchy can introduce new aspects of behavior.

    Now further:

     class A { public virtual void Method() { Console.WriteLine("Method A invoked"); } } class B : A { public new virtual void Method() { Console.WriteLine("Method B invoked"); } } class C : B { public override void Method() { Console.WriteLine("Method C invoked"); } } 

    But this example is difficult to say exactly what the author of these lines wanted to say from the point of view of business logic, but it sounds like this: class B adds a new method Method , but unfortunately it uses a method whose name is already in the base class. But class B does not want to "change" the behavior of the method from the base class, but to create its own method, which has nothing to do with the base class method, except for the name.

    This practice leads to ambiguous behavior, since now the choice of method is determined not only by the dynamic type of the object (runtime type), but also by the type of the variable (type known to the compiler): if a variable of type A , the method from class A will be called. If the type of the variable is B or C , then another branch of methods will be used (below will be an explanation of why this is so).

    In other words, from the point of view of the Method method, there are two branches: one starts with type А and is limited to it, and there is another polymorphic branch that starts in type B and continues in the heir - type C.

    Now a little about how it works in the CLR.

    For each type, the CLR stores a method table (Method Table), where each record describes a separate method — its signature and whether the slot overrides this method from the base class.

    When you declared a method with the prefix new in class B , the CLR added the "new" method to the label, while marking this method as new, not related to the method from the base class. In the case of class С , the Method method on the nameplate of a method of type C marked as polymorphic, override the behavior of the immediate base class.

    Now it’s worthwhile to say how the resolution of the method occurs at runtime: if a.Method is a.Method static type of the variable a will first be determined, after which the Method method will be found in the method table. If the static type of the variable is A , then the table of methods of type A will be viewed first. And in this case, the CLR will see that this method is virtual. After that, the actual type of the object will be determined (for example, type C ) and the CLR will see if this type has a redefinition of the method declared in type A The CLR will receive a negative response, because type C does not override the Method method declared in type A (after all, this type overrides Method Method type B ).

    So it turns out that the result of the resolution of the method name now depends not only on the type of execution time, but also on the type of the compile time variable.

    • As for the code (in the first case), this is a purely synthetic example, so to speak, on understanding how everything is organized in the CLR. Such question: MethodTable is stored in type, each type has its own method table. If we have an apkast, then the CLR simply examines the table of methods of the derived class (more precisely, it looks at the lexemes that generated the metadata, and then checks the "ReuseSlot" (override) flags, if not, then returns to the base table and is already working with it? So to say in nutshell :) And also, static methods have nothing to do with the method table, is that right? - CSharpUser
    • 1. In the case of an apkast, another algorithm for viewing the method table goes. Starts with a variable type. And yes, the algorithm is described correctly. 2. For static methods there is a separate table of methods. - Sergey Teplyakov
    • Thank you very much, Sergey! I read your articles and translations. By the way, if you have time, check out here.stackoverflow.com/questions/607529/… - CSharpUser
    • @CSharpUser I will answer. This is a rather complicated question, so give me a plz a day and a half. - Sergey Teplyakov
    • Yes, no problem :) - CSharpUser