带着问题读CLR via C#(笔记二)类型基础
发布时间:2022-01-31 07:31:08
Q1: Object类型包含哪些方法?
A1: Object类型共包含6个方法,Equals, GetHashCode, ToString, GetType, MemberwiseClone和Finalize.
Q2: new一个对象的过程是什么?
A2: 1)计算对象所需字节数,包括该类型及其基类型定义的所有实例字段所需的字节数和类型对象指针、同步块索引所需字节数,类型指针和同步块索引是CLR用来管理对象的;2)在托管堆上分配该对象所需内存空间;3)初始化类型对象指针和同步块索引;4)执行构造函数。大多数编译器都在构造函数中自动生成一段代码调用基类构造函数,每个类型的构造函数在执行时都会初始化该类型定义的实例字段。5)返回指向新建对象的一个引用,保存在对象变量中。
可用如下代码验证第四步:
View Code
class Program
{
static void Main(string[] args)
{
TestThree t = new TestThree();
Console.Read();
}
}
class Test
{
int i;
public int I { get; set; }
public Test()
{
Console.WriteLine("This is Test's constructor");
}
}
class TestTwo : Test
{
public TestTwo()
{
Console.WriteLine("This is TestTwo's constructor");
}
}
class TestThree : TestTwo
{
public TestThree()
{
Console.WriteLine("This is TestThree's constructor");
}
}
执行结果如下:
Q3: 父类型和子类型间如何进行转换?
A3: C#允许将一个对象从它的本身类型转换为它的父类型,这是安全的,不需要做任何额外操作,但要将一个对象从它的本身类型转换为它的子类型,则必须要显式转换,因为可能会失败。见代码:
View Code
class Program
{
static void Main(string[] args)
{
Person person = new Person();
Man man = new Man();
Person p = man;
Man m = person;
}
}
class Person
{ }
class Man : Person
{ }
这段代码是无法编译通过的,在Main方法的第四行会报一个这样的错误:
Error 1 Cannot implicitly convert type 'TypeBasic.Person' to 'TypeBasic.Man'. An explicit conversion exists (are you missing a cast?) C:\Users\Allen\Documents\Visual Studio 2012\Projects\TypeBasic\TypeBasic\Program.cs 16 21 TypeBasic
很显然,一个 “男人” 一定是一个人,故可以直接转换,但一个 “人” 并不一定是一个 “男人”,所以必须要显式转换。可将代码这样改写:
// From
Man m = person;
// To
Man m = (Man)person;
这样就可以成功通过编译,但是在运行的时却抛出了异常,很显然,Person不能被转换为Man. 什么情况下Person可以被转换为Man? 见如下代码:
View Code
static void Main(string[] args)
{
Man man = new Man();
Test(man);
}
static void Test(Person p)
{
Man m = (Man)p;
}
Q4: is和as操作符的作用是什么?
A4: is操作符用来判断一个对象是否属于某种类型,返回一个布尔值。改写下上例的Test方法:
View Code
static void Test(Person p)
{
if (p is Man)
{
Man m = (Man)p;
}
}
以上代码共进行了两次类型检测,is操作符首先检测p是否为Man类型,在if的方法体中进行强制转换时,CLR会再次检测p的类型,这对性能有一定影响。
as操作符很好的解决了这个问题,再次改写Test方法:
View Code
static void Test(Person p)
{
Man m = p as Man;
if (m != null)
{
//...
}
}
as操作符在检测p的类型后会直接对p进行类型转换,返回一个Man类型的对象,若检测出p不是Man类型,则会返回null. 整个过程只进行了一次类型检测。
Q5: 什么是命名空间?
A5: 命名空间是对类型的逻辑分组,对于编译器而言,命名空间的作用是使类型名称变得更长更具唯一性,但CLR并不知道命名空间,访问一个类型时,CLR需要知道该类型的全名以及它所在程序集。
Q6: 命名空间和程序集之间的关系是什么?
A6: 命名空间和程序集间并没有什么关联,同一命名空间的类型可以存在于不同程序集,同一程序集中的类型也可以属于不同命名空间。
Q7: 分析以下代码执行时CLR发生的动作。
View Code
namespace TestConsole
{
class Program
{
static void Main(string[] args)
{
Employee e;
Int32 year;
e = new Employee();
e = Employee.Lookup("Joe");
year = e.GetYearsEmployed();
e.GenProgressReport();
}
}
class Employee
{
// 实例方法
public Int32 GetYearsEmployed()
{
//...
}
// 虚方法
public virtual string GenProgressReport()
{
//...
}
// 静态方法
public static Employee Lookup(string name)
{
//...
}
}
class Manager : Employee
{
// 对父方法重写
public override string GenProgressReport()
{
//...
}
}
}
A7:
1)CLR检查该方法内部引用的所有类型(Employee, Int32, Manager, String),确保定义了这些类型的程序集已成功加载;
2)CLR利用程序集的元数据提取这些类型的相关信息,并创建一些数据结构来表示类型本身,如下图所示:
3)执行"序幕代码",在线程栈中为局部变量分配内存,并初始化它们,如下图所示:
4)构建Manager对象,在托管堆中创建一个Manager类型的实例,CLR会初始化该实例的类型对象指针,让它引用与实例对应的类型对象,本例中为Manager类型对象;此外CLR会初始化同步块索引,并将该实例所有实例字段设为null或0,再调用构造函数,new操作符会返回该实例内存地址,该地址保存在e中,如下图:
5)Lookup是一个静态方法,调用时CLR会定位定义该静态方法的类型对应的类型对象,然后JIT编译器在该类型对象的方法表中查找被调用的方法的记录项,对方法进行JIT编译(第一次执行),执行编译后的代码。本例中,假定查出的实例是一个i额Manager类型,则在堆中创建一个Manager实例,用查出的信息初始化该实例,并返回它的地址储存在e中,此时,第一个初始化的Manger对象将没有指针指向它,它成为垃圾回收对象。见下图:
6)GetYearsLookup是一个非虚实例方法,在调用时,JIT编译器会找到发出调用的标量(e)的类型对应的类型对象,本例中为Employee类型对象,因为e被定义为了Employee类型。如果Employee中没有定义该方法,则会继续向上一层查找,知道查找到Object类型对象,查找到该方法后,JIT编译器对其进行编译(第一次执行),再执行变异后的代码,将执行结果保存在局部变量中。见下图:
7)GetProgressReport为定义在Employee中的虚方法,调用一个虚方法,JIT编译器会在方法中生成一些额外代码,这些代码在每次调用方法时都会执行。它首先会检测发出调用的变量,根据地址查找到发出调用的实例,本例为一个Manager对象,然后检测对象内部的类型指针,找到该对象的实际类型,从实际类型对象的方法列表中查找调用的方法的记录项,进行JIT编译(第一次执行),执行变异后代码。见下图:
Q8: 如何理解类型对象?
A8: 类型对象本质上也是对象,它也包含类型对象指针成员,CLR创建这些类型对象时,也会对其进行初始化。CLR开始在一个进程中运行时,会立即为MSCorLib.dll中定义的System.Type对象创建一个特殊的类型对象,Q7中的Emloyee和Manager都是Type类型的“实例”,它们的类型对象指针都会指向Type类型对象,而Type类型对象的类型对象指针则会指向自己。


猜你喜欢
- 前言:最近涉及到和QQ打交道,定义所有的好友一共只能有300条消息,如果一次性从数据库读取300条或者更多,界面会有细微的卡顿.所以考虑了下
- 一、前言最近在看android fragment与Activity进行数据传递的部分,看到了接口回调的内容,今天来总结一下。二、回调的含义和
- 工作原理:Spring Cloud框架下的服务发现Eureka包含两个组件分别是: Eureka Server与Eureka ClientE
- 一、先明确几个基本概念1、伪随机数:pseudo-random number generators ,简称为:PRNGs,是计算机利用一定的
- 跨域的产生就是因为浏览器的同源策略。它是浏览器的核心安全功能,所谓的同源,就是指域名,协议,还有端口要相同。传统的方案就是JSONP(前端处
- 简介Log4J 是 Apache 的一个开源项目(官网 http://jakarta.apache.org/log4j)
- Mybatis多层嵌套查询三张表:user article blog表的存储sql文件/*Navicat MySQL Data Transf
- 管理fragment的生命周期有些像管理activity的生命周期。Fragment可以生存在三种状态:Resumed:Fragment在一
- 由于我使用的是properties类型的配置文件,在对druid的参数进行配置的时候,多加了druid,也就是spring.datasour
- 上一篇文章我们介绍了Apache Commons Math3学习之数值积分实例代码,这里给大家分享math3多项式曲线拟合的相关内容,具体如
- 分析:label标签控件是主线程创建的,不能直接从另一个线程访问.可以这样认为:不能跨线程直接访问控件;最简单的办法就是:using Sys
- C#的多态性:我的理解是:同一个操作,作用于不同的对象时,会有不同的结果,即同一个方法根据需要,作用于不同的对象时,会有不同的实现。C#的多
- Android 获取手机信息应用信息:包名、版本号、版本名,手机是否有Root权限手机信息:手机屏幕宽和高、当前可用内存大小、总内存大小、I
- 前言有些朋友可能是从事开发工作的时间不是特别的长,所以觉得Service相对与另外两个组件activity、broadcast receiv
- 条码的应用已深入生活和工作的方方面面。在处理条码时,常需要和各种文档格式相结合。当需要在文档中插入、编辑或者删除条码时,可借助于一些专业的类
- 本文实例为大家分享了unity通过Mesh网格绘制球体的具体代码,供大家参考,具体内容如下接着上一篇文章说:球体public class 球
- Mybatis事务管理我们可以在mybatis-config.xml中配置事务管理器的实现<transactionManager ty
- 本文列举了我在周围同事的Java代码中看到的一些比较典型的错误。显然,静态代码分析(我们团队用的是qulice)不可能发现所有的问题,这也是
- CyclicBarrier是什么CyclicBarrier是Java并发包中提供的一种同步工具类,它可以让多个线程在某个屏障处等待,直到所有
- Java Lambda 源码分析问题:Lambda 表达式是什么?JVM 内部究竟是如何实现 Lambda 表达式的?为什么要这样实现?一、