I believe everyone is familiar with the three features of object-oriented programming: encapsulation, inheritance, and polymorphism. Everyone can say a thing or two, but most only know what they are, not how the CLR implements them internally. So this article mainly discusses some concepts in polymorphism and the internal implementation mechanism.
1. Concept of Polymorphism
First, explain what polymorphism is: the same operation applied to different objects can have different interpretations and produce different execution results. This is polymorphism. In other words, an instance of the same type calls the "same" method, but the results are different. The "same" here is in quotes because the methods that look the same are actually different methods being called.
When it comes to polymorphism, we cannot avoid mentioning the following concepts: overloading, overriding, virtual methods, abstract methods, and hiding methods. Let's introduce their concepts one by one.
1、OverloadIn the same scope (generally a class), two or more methods with the same method name but different parameter lists are called overloading. They have three characteristics (commonly known as "two musts and one can"):
- Method names must be the same
- Parameter lists must be different
- Return types can be different
For example:
public void Sleep()
{
Console.WriteLine("Animal睡觉");
}
public int Sleep(int time)
{
Console.WriteLine("Animal{0}点睡觉", time);
return time;
}
2、OverrideIn a subclass, to meet its own needs, redefining a different implementation of a method requires the override keyword. The overridden method must be a virtual method, using the virtual keyword. Its characteristics are (three same):
- Same method name
- Same parameter list
- Same return type
For example: definition in the parent class:
public virtual void EatFood()
{
Console.WriteLine("Animal吃东西");
}
Definition in the child class:
public override void EatFood()
{
Console.WriteLine("Cat吃东西");
//base.EatFood();
}
Tip:Frequently, some friends ask about the difference between overloading and overriding, and on the internet this is one of the common C# interview questions. In fact, these two concepts have no relationship at all; they merely both contain the character "over". There is no significance in comparing them. Just distinguish their different definitions.
3、Virtual methodA method defined in the base class that allows overriding in derived classes, defined using the virtual keyword. For example:
public virtual void EatFood()
{
Console.WriteLine("Animal吃东西");
}
NoteVirtual methods can also be called directly. For example:
Animal a = new Animal(); a.EatFood();
The output is:
Animal吃东西
4、Abstract methodA method defined in the base class and must be overridden in derived classes. It is defined using theabstractkeyword. For example:
public abstract class Biology
{
public abstract void Live();
}
public class Animal : Biology
{
public override void Live()
{
Console.WriteLine("Animal重写的抽象方法");
//throw new NotImplementedException();
}
}
Note:Abstract methods can only be defined in abstract classes. If not defined in an abstract class, the following error is reported:

The difference between virtual methods and abstract methods isBecause abstract classes cannot be instantiated, abstract methods cannot be called; in other words, abstract methods can never be implemented.
5、Hiding methodA method defined in a derived class with the same name as a method in the base class, defined using the new keyword. For example, in the base class Animal there is a method Sleep():
public void Sleep()
{
Console.WriteLine("Animal Sleep");
}
Then the code to define the hiding method in the derived class Cat is:
new public void Sleep()
{
Console.WriteLine("Cat Sleep");
}
Or:
public new void Sleep()
{
Console.WriteLine("Cat Sleep");
}
Note:
- (1) A hiding method can hide not only virtual methods in the base class, but also non-virtual methods in the base class.
- (2) For a hiding method, an instance of the parent class calls the parent class method, and an instance of the child class calls the child class method.
- (3) Compared with the previous point: For an overriding method, a variable of the child class calls the overridden method of the child class. For a variable of the parent class, it depends on whether the parent class reference points to an instance of the child class or its own instance. If it references an instance of the parent class, the base class method is called; if it references an instance of the derived class, the derived class method is called.
Okay, the basic concepts are finished. Let's look at an example. First, we create several classes:
public abstract class Biology
{
public abstract void Live();
}
public class Animal : Biology
{
public override void Live()
{
Console.WriteLine("Animal重写的Live");
//throw new NotImplementedException();
}
public void Sleep()
{
Console.WriteLine("Animal Sleep");
}
public int Sleep(int time)
{
Console.WriteLine("Animal在{0}点Sleep", time);
return time;
}
public virtual void EatFood()
{
Console.WriteLine("Animal EatFood");
}
}
public class Cat : Animal
{
public override void EatFood()
{
Console.WriteLine("Cat EatFood");
//base.EatFood();
}
new public void Sleep()
{
Console.WriteLine("Cat Sleep");
}
//public new void Sleep()
//{
// Console.WriteLine("Cat Sleep");
//}
}
public class Dog : Animal
{
public override void EatFood()
{
Console.WriteLine("Dog EatFood");
//base.EatFood();
}
}
Now let's look at the code to be executed:
class Program
{
static void Main(string[] args)
{
//Animal的实例
Animal a = new Animal();
//Animal的实例,引用派生类Cat对象
Animal ac = new Cat();
//Animal的实例,引用派生类Dog对象
Animal ad = new Dog();
//Cat的实例
Cat c = new Cat();
//Dog的实例
Dog d = new Dog();
//重载
a.Sleep();
a.Sleep(23);
//重写和虚方法
a.EatFood();
ac.EatFood();
ad.EatFood();
//抽象方法
a.Live();
//隐藏方法
a.Sleep();
ac.Sleep();
c.Sleep();
Console.ReadKey();
}
}
First, we define several instances of the classes we need to use. It should be noted:
- (1) The Biology class is an abstract class and cannot be instantiated;
- (2) Variable ac is an instance of Animal, but it points to a Cat object. Because Cat is a derived class of Animal, this conversion is fine. This is also the key point of polymorphism.
Next, let's analyze step by step:
1、
//重载 a.Sleep(); a.Sleep(23);
Obviously, the two Sleep methods called by the Animal variable a are overloaded methods. The first statement calls the parameterless Sleep() method, and the second statement calls the Sleep method with an int parameter. Note that the return types of the two Sleep methods are different, which also illustrates the last of the three characteristics of overriding—the return type can be different.
The running result is as follows:
Animal Sleep Animal在23点Sleep
2、
//重写和虚方法 a.EatFood(); ac.EatFood(); ad.EatFood();
In this section, a, ac, and ad are all instances of Animal, but they reference different objects. a references an Animal object, ac references a Cat object, and ad references a Dog object. What difference will this make in the execution results? See the execution results below:
Animal EatFood Cat EatFood Dog EatFood
The first statement: an Animal instance directly calls Animal's virtual method EatFood, no problem.
In the second and third statements, although they are also instances of Animal, they point to Cat and Dog objects respectively, so the EatFood methods overridden in the Cat and Dog classes are called, just as if Cat and Dog instances directly called EatFood. This is the embodiment of polymorphism: the same operation applied to different objects can have different interpretations and produce different execution results.
3、
//抽象方法 a.Live();
This is relatively simple. It directly overrides the Live method of the parent class Biology. The execution result is as follows:
Animal重写的Live
4、
//隐藏方法 a.Sleep(); ac.Sleep(); c.Sleep();
When analyzing the hiding method, it should be compared with virtual methods and overriding. There is no objection to variable a calling Animal's Sleep method and variable c calling Cat's Sleep method. But variable ac references an object of Cat type. Should it call the EatFood method of Animal type or the EatFood method of Cat type? The answer is that it calls the parent class, namely Animal's EatFood method. The execution result is as follows:
Animal Sleep Animal Sleep Cat Sleep
Most articles stop here, merely letting us know these concepts and calling methods, without explaining why this is the case. Next, let's go a little deeper and talk about the mechanism behind polymorphism.
2. In-depth Understanding of Polymorphism
To deeply understand polymorphism, we must start with value types and reference types. We all know that value types are stored on the thread stack, while reference types are stored in the managed heap. Because all classes are reference types, we only look at reference types.
Now return to the earlier example. Main is the program entry point. When the JIT compiler compiles the Main function into native CPU instructions, it finds that this method references the Biology, Animal, Cat, and Dog classes, so the CLR creates several instances to represent these types themselves. We call them "type objects". This object contains the static fields of the class, a method table containing all methods of the class, and also two additional members that all objects in the managed heap must have—the Type Object Pointer and the Sync Block Index.
The above passage may not be understood by some friends who have not read relevant CLR books, so let's draw a diagram to describe it:

The above diagram shows what the CLR does before executing the Main function. Next, start executing the Main function (for convenience, simplify the Main function):
//Animal的实例 Animal a = new Animal(); //Animal的实例,引用派生类Cat对象 Animal ac = new Cat(); //Animal的实例,引用派生类Dog对象 Animal ad = new Dog(); a.Sleep(); a.EatFood(); ac.EatFood(); ad.EatFood();
Now instantiate three Animal instances, but they actually point to an Animal object, a Cat object, and a Dog object respectively, as shown below:

Please note that although variables ac and ad are both of Animal type, they point to a Cat object and a Dog object respectively. This is the key.
When executing a.Sleep(), because Sleep is a non-virtual instance method, the JIT compiler will find the type object (Animal type object) corresponding to the type (Animal) of the variable (a) that made the call. Then it calls the Sleep method in that type object. If that type object does not have a Sleep method, the JIT compiler traverses back through the base classes of the class (all the way to Object) to find the Sleep method.
When executing ac.EatFood, because EatFood is a virtual instance method, the JIT compiler generates some extra code in the method when calling. This code first checks the variable (ac) that made the call, then follows the variable's reference address to find the object that made the call (Cat object), finds the type object corresponding to the object that made the call (Cat type object), and finally looks for the EatFood method in that type object. Similarly, if the EatFood method is not found in that type object, the JIT compiler traverses back to the base class of that type object to find it.
What is described above is the different execution methods of the JIT compiler when encountering calls to non-virtual instance methods and virtual instance methods. It is this different handling of these two types of methods that produces one of the three features of object-oriented programming that we see on the surface—polymorphism.
Original URL: https://www.cnblogs.com/zhangkai2237/archive/2012/12/20/2826734.html