带着问题读CLR via C#(笔记二)类型基础

 更新时间:2020年6月25日 11:42  点击:1461

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类型对象的类型对象指针则会指向自己。

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25