颠覆你对方法调用的看法!

开发 后端
如果你对实例方法,虚方法的运行机制已经了如指掌,并且,对方法和对象的内存布局也心中有数,那么本文可能会颠覆你以前对他们的认识。阅读本文的最佳方式就是亲自演练一下,如果看完之后有疑惑,那么是正常的,但是稍加思考就会想明白。

注意:如果你是一个初学者,对实例方法,虚方法的调用还不太清楚,强烈建议你不要阅读本文,因为这里面的代码会让你完全崩溃掉。

如果你对实例方法,虚方法的运行机制已经了如指掌,并且,对方法和对象的内存布局也心中有数,那么本文可能会颠覆你以前对他们的认识。

阅读本文的***方式就是亲自演练一下,如果看完之后有疑惑,那么是正常的,但是稍加思考就会想明白。

 

我说,string变量可以直接引用一个object对象!

我说,派生类型的变量可以直接引用基类型的对象!

你会说,老兄,别开玩笑了,派生类型怎么可以指向一个基类型的对象呢!

 

我会让你见证一下奇迹,并在文章的结尾再给你一个更加不可思议的例子。

 

首先,请看下面的代码:

  1. class Program {  
  2.         static void Main(string[] args) {  
  3.             Derived d=(Derived)new Base();  
  4.             d.Print();  
  5.             Console.Read();  
  6.         }  
  7.     }  
  8.             class Base {  
  9.         public void Print() {  
  10.             Console.Write("in base");  
  11.         }  
  12.     }  
  13.  
  14.     class Derived : Base {  
  15.         public new void Print() {  
  16.             Console.WriteLine("in derived");  
  17.         }  
  18.     } 

毫无疑问,在运行时一定会抛出一个异常,因为Base对象无法转换为Derived对象。

 

但是,现在,我就想让d指向Base对象,并且可以调用Base中的Print方法,该怎么做呢?

 

用FiledOffset可以做到这一点,但首先需要定义一个叫做Manager的类,里面包含两个实例字段,一个为Derived,一个为Base。如下:

  1. [StructLayout(LayoutKind.Explicit)]  
  2.     class Manager {  
  3.         [FieldOffset(0)]  
  4.         public  Base b = new Base();  
  5.  
  6.         [FieldOffset(0)]  
  7.         public Derived derived;  
  8.     } 

现在,通过为b和derived都指定了相同的偏移,所以,b和derived都指向了同一个对象,Base对象。

由于derived现在指向了Base对象,那么如果我调用d.Print方法,调用的是Base的Printf还是Derived的Print方法,还是抛出一个异常。请看如下代码:

  1. class Program {  
  2.         static void Main(string[] args) {  
  3.             Manager m = new Manager();  
  4.             m.derived.Print();  
  5.             Console.Read();  
  6.         }  
  7.     } 

运行上面代码,会输出什么呢?

答案是,“In Derived”。

这很不可思议,因为derived指向的是Base对象,现在调用的确实Derived的方法。想要了解原因,请看下图:

这里,尽管derived指向的是一个Base对象,但是,CLR发现Print是一个非虚方法,所以CLR并不关心derived变量指向什么对象,CLR根据derived变量的类型来调用Print方法,这里derived是一个 Derived类型,所以CLR会调用Derived中的Print,最终输出In Derived。

第二个例子:

下面的这个例子也很不可思议,同样会颠覆你传统的观点。

让我们将上面的print方法改为virtual方法,最终如下:

  1. [StructLayout(LayoutKind.Explicit)]  
  2.     class Manager {  
  3.         [FieldOffset(0)]  
  4.         public  Base b = new Base();  
  5.  
  6.         [FieldOffset(0)]  
  7.         public Derived derived;  
  8.     }  
  9.  
  10.     class Base {  
  11.         public virtual void Print() {  
  12.             Console.Write("in base");  
  13.         }  
  14.     }  
  15.  
  16.     class Derived : Base {  
  17.         public override void Print() {  
  18.             Console.WriteLine("in derived");  
  19.         }  
  20.     } 

现在,运行如下测试代码:

  1. class Program {  
  2.         static void Main(string[] args) {  
  3.             Manager m = new Manager();  
  4.             m.derived.Print();  
  5.             Console.Read();  
  6.         }  
  7.     } 

这次结果会是什么呢?强烈建议你自己思考答案。

结果是,In Base!

是不是及其不可思议!为了更清楚的理解原因,请看下图:

 

 

 

这里,尽管derived指向的是Base对象,但是,当CLR看到derived.Print这行代码时,由于Print是虚方法,所以CLR会查看derived所指向的Base对象。

CLR发现Base对象里的type object pointer指向一个Base type object,于是就调用Base Type object中的Print方法,所以最终会输出InBase。

 总结:

没有总结可不好。

本质上,子类型是不能引用父类型对象的。但是,我们可以通过FieldOffset绕过这一限制。通过子类型的变量来调用父对象的方法,这很是不可思议,但更不思议的是,当子类型的变量指向父对象时,竟然可以调用子方法!

那么上面的本质是什么呢?当CLR调用一个非虚方法时,不会关心变量具体指向的是什么,因为CLR此时是通过变量的类型来调用方法。如果方法时虚方法,那么CLR为了实现多态,需要查看这个变量指向的是什么对象,然后在通过对象的type object pointer找到对应的Type Object,然后调用Type Object中的方法。

原文链接:http://www.cnblogs.com/francisYoung/p/3371106.html

责任编辑:林师授 来源: 博客园
相关推荐

2018-12-14 15:51:47

Pandas数据数据结构

2023-10-13 08:52:19

远程Bean类型

2009-09-24 08:45:26

微软竞争对手Chrome

2010-09-25 15:52:01

2011-04-29 11:14:34

投影机

2021-09-26 15:34:21

内部结构方法区

2011-01-19 15:31:46

Kmail

2010-09-30 10:44:43

2022-03-28 12:23:25

企业内部威胁监管

2019-03-10 08:30:35

物联网IoT物联网设备

2013-07-02 14:45:21

Foxmail 7.1邮件

2013-05-24 09:43:46

2022-04-15 11:37:19

密码网络攻击网络安全

2022-08-18 09:51:50

Python代码循环

2015-05-14 12:41:45

智能

2010-09-13 18:11:38

2017-06-20 08:34:23

2019-10-10 15:57:09

云安全混合云架构

2013-09-02 15:35:00

2024-06-28 12:54:45

点赞
收藏

51CTO技术栈公众号