当前位置: 首页 > news >正文

探讨C#2.0对象模型

一名.NET程序员给我发了一封邮件,讨论C#2.0的对象模型:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

金老师:
您好!
我是一名.net程序员。拜读了您的“.net 2.0面向对象编程揭秘”这本书,受益匪浅。
您的书写得非常深入,经常让我有恍然大悟的感觉。很多堆积已久的问题迎刃而解。同时我也有了很多疑问。希望您能帮我解答。
1.您说子类会调用父类的构造函数。同时“子类对象集成了基类的实例字段”(P213)。“基类的实例字段”包括父类的private字段么?

2.Son son=new Son();
Father father=son;
这是您介绍的多态编程。子类实例变量赋值给父类变量。
这样的两句代码,在内存中将会发生什么呢?
对象的实例没有改变么?faher和son存的是同一个首地址?
这时father能调用的都是Father类型的字段和方法。那么father的类型表指针应该指向Father类型表,这样可以解释father调用父类的方法。那么字段呢?
father is Son
这又是如何实现的呢?
3.值类型在编译期已经在栈上分配好了内存
编译期还是IL指令,还没有转化为cpu可执行的二进制代码。这时内存是如何分配好的?
编译期为什么要分配内存呢?不是应该运行的时候才需要么?
4.您的书中在说明IL代码的时候多次提及计算堆栈。
您能把IL代码执行的基本原理告诉我吗?或者从哪里可以看到相关的资料。
5.对象实例存储在堆中,那么对象变量呢?是存储在线程堆栈中么?
同时我也发现了您的一个疏漏。
第183页 第10行 首先调用父类构造函数,再调用子类构造函数。
第211页 第6行 在构造函数中,先初始化自身的字段,在调用基类的构造函数。
这两句表达有误。
应该是:
先调用子类构造函数,通过子类构造函数调用父类构造函数。先执行父类构造函数的代码,初始化父类的字段,再回到子类初始化子类的字段。
我编写了这样两个简单的类
public class Father
{
protected string a;
public Father()
{
a = "a";
}
}
public class Son : Father
{
private string b;
public Son()
{
b = a;
}
}
子类的构造函数IL代码如下:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代码大小 17 (0x11)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void ConsoleApplication1.Father::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldc.i4.1
IL_000a: stfld int32 ConsoleApplication1.Son::b
IL_000f: nop
IL_0010: ret
} // end of method Son::.ctor
这段IL代码好很的证明了我的观点。

希望您能耐心的解答我的问题。谢谢
++++++++++++++++++++++++++++++++++++++++++
我的回复如下:
-----------------------------
1 “基类的实例字段”包括父类的private字段,但子类方法不能存取这个字段,这是由编译器在生成IL指令时保证的。
2 Son son=new Son();
Father father=son;
实际上,在内存中只有一个Son对象实例(放在堆中),但存在两个类型表(由CLR直接管理,当卸掉程序集时,这些类型表占用的资源被回收),赋值后,father和son存的是同一个首地址,指向在托管堆中的Son对象实例。
father is son不是在程序运行时实现的,而是在编译时实现的,由C#编译器直接将这个代码翻译为IL指令,此IL指令会根据你的代码生成对合适方法的调用指令。
当程序运行时,CLR直接装入的是翻译好的IL指令,而非C#代码,IL指令本身是没有“多态”特性的,因为它已经比较靠近底层,应该尽可能地简化以提高效率。
3 值类型在编译期已经在栈上分配好了内存
这句是错的,变量的内存分配是在程序运行时才进行的。是我的疏漏。
4 CLR可以看成是一个基于堆栈的虚拟计算机,这台机器运行的是IL汇编程序,凡是IL代码中所说的堆栈,都是指“计算堆栈(Evaluation Stack)”。在书的附录中有一个“MSIL基础教程”,其中介绍了相关的原理。有关IL的资料很少,国内可以看到的书就是《Inside Microsoft .NET IL Assembly》,千万别看中文版,我看译者肯定没弄明白其中的技术内容,译得一踏糊涂。要看就看英文版,但这本书阅读难度很大,作者是技术牛人,但作为一名作家,我认为不合格。本书中有关IL编程的介绍是我经过收集相关资料进行消化,并经实践检验之后写的,但管中窥豹,仅供参考。
5.你说得对:对象实例的数据存储在托管堆中,引用此对象的对象变量则是存储在线程堆栈中。
关于子类字段与父类字段初始化顺序的问题,我注意到你在子类构造函数中使用了基类的数据成员,因此才会导致先初始化基类数据成员,后初始化子类数据成员。这是一种特例。
事实上,如果子类字段与父类字段没有这种依存关系,C#编译器是按照以下顺序生成IL指令的:
new 子类对象时,子类构造函数被调用,在执行子类构造函数的代码时,先初始化子类的字段,然后再调用父类的构造函数初始化父类的字段。
如果子类字段依赖于父类字段的值,C#编译器在生成IL指令时,会先调用父类的构造函数初始化父类的字段,再调用子类构造函数初始化依赖于父类字段的这些字段。
我修改了一下你的代码,给子类和父类增加两个独立的字段:
public class Father
{
protected string a;
public int fatherFld=100;
public Father()
{
a = "a";
}
}
public class Son : Father
{
private string b;
private int sonFld = 200;
public Son()
{
b = a;
}
}
子类生成的IL代码如下:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代码大小 33 (0x21)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4 0xc8
IL_0006: stfld int32 ConsoleApplication1.Son::sonFld
IL_000b: ldarg.0
IL_000c: call instance void ConsoleApplication1.Father::.ctor()
IL_0011: nop
IL_0012: nop
IL_0013: ldarg.0
IL_0014: ldarg.0
IL_0015: ldfld string ConsoleApplication1.Father::a
IL_001a: stfld string ConsoleApplication1.Son::b
IL_001f: nop
IL_0020: ret
} // end of method Son::.ctor
可以看到IL_0006句先初始化子类的字段,IL_000c再调用父类的构造函数初始化父类字段,再回过头来于IL_0015和IL_001a两句完成用父类字段初始化子类字段的工作。
父类构造函数IL代码如下:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代码大小 29 (0x1d)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.s 100
IL_0003: stfld int32 ConsoleApplication1.Father::fatherFld
IL_0008: ldarg.0
IL_0009: call instance void [mscorlib]System.Object::.ctor()
IL_000e: nop
IL_000f: nop
IL_0010: ldarg.0
IL_0011: ldstr "a"
IL_0016: stfld string ConsoleApplication1.Father::a
IL_001b: nop
IL_001c: ret
} // end of method Father::.ctor
注意一下基类object构造函数的调用是插在两个字段初始化指令中间的。我经过实验发现,C#编译器生成IL代码时对于字串类型字段的初始化总是在调用基类构造函数之后,而象int之类的字段,如果是独立的,其初始化指令总在调用基类构造函数指令之前。
为什么这样,只好去问问C#编译器的设计者了。
----------------------------
欢迎就此问题进行讨论。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • XML文件转换成Word文件或者Excel文件
  • python excel数据处理_Excel的分列功能很强大?Python数据处理分析,pandas有更牛逼的...
  • datetime转int_Python实例PDF转JPG
  • 艰难的shader系统
  • visual studio 怎么生成coredump文件_如何在docker容器中生成core dump
  • 对信息系统集成项目管理的一点看法
  • 电子工程师之家_「德语学习」电气、电子、电力什么区别?
  • javascript操作radio的几种方法
  • arcengine遍历属性表_【Python@arcpy】python操作Excel与属性表批处理
  • ExtremeTable的导出问题
  • 运动估计算法的程序实现_光流法--Lucas Kanade算法
  • 网吧系统母盘制作(系统分区整体考虑优化配置篇)
  • 点在多边形内_空间分析:2-4.Python生成泰森多边形
  • struts2的s:param标签使用
  • leetcode 打印_剑指 Offer 32 - I. 从上到下打印二叉树
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • Angular Elements 及其运作原理
  • co.js - 让异步代码同步化
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • JAVA之继承和多态
  • js继承的实现方法
  • Nodejs和JavaWeb协助开发
  • 程序员该如何有效的找工作?
  • 订阅Forge Viewer所有的事件
  • 巧用 TypeScript (一)
  • 如何在GitHub上创建个人博客
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 一个完整Java Web项目背后的密码
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 关于Android全面屏虚拟导航栏的适配总结
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (6)添加vue-cookie
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (笔试题)合法字符串
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (二)JAVA使用POI操作excel
  • (附源码)spring boot儿童教育管理系统 毕业设计 281442
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (四)库存超卖案例实战——优化redis分布式锁
  • (转)jdk与jre的区别
  • .apk文件,IIS不支持下载解决
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net wcf memory gates checking failed
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • .NET中使用Protobuffer 实现序列化和反序列化
  • .sh
  • @Autowired多个相同类型bean装配问题
  • @html.ActionLink的几种参数格式
  • @JSONField或@JsonProperty注解使用