【読書経験] CLRからランタイムタイプ基本C位を介して

あなたは、静的関数の特定のタイプ、またはisntance、仮想関数非の仮想関数を呼び出すときに
、関連する機能の内容を検索し、実行するために、スタックとヒープの間にCLRのJITとどのように?


序文

タイトル小鳥が、主にC#を経由してCLRの話を(私は第三版、最新版は第四版に出てきた参照)本の第4章:タイプFundametalsの時間、いくつかの説明ランタイムがあり、仮想関数を呼び出し、スレッド・スタックとヒープを変更し、非仮想関数、関数のような静的関数、2つのクラスの継承関係、異なるタイプ。

一冊の本のこのセクションでは、私のお気に入りの通路の一つ、私はまた、最も重要なの認知型の記述に数え啓発です。

この記事ではJiehuaxianfo、次の点に残された内容の要約を見ていきます。

  1. 型の変数を宣言します
  2. 個々のタイプの実装(インスタンス)の
  3. インスタンス関数を呼び出し
  4. 非仮想関数呼び出し
  5. 仮想関数を呼び出し、関数をオーバーライド
  6. 静的関数を呼び出します

バブルメモリは、C#のを経由して本のCLRから派生しています。

このセクションを読んで、最初の継承、ポリモーフィズム、仮想、オーバーライド、スタティック、ヒープ、スタックの基本的な定義を理解してください。

クラス定義

まず、2クラス、それぞれ、従業員およびマネージャがあります。以下に示すように、従業員、マネージャークラスの継承関係:

従業員とマネージャのクラス図

プログラムの二つのクラス次のように:

    internal class Employee
    {
        public Int32 GetYearsEmployed()
        {
            return 5;
        }

        public virtual String GetProgressReport()
        {
            return "Employee's GetProgressReport";
        }

        public static Employee Lookup(String name)
        {
            return new Manager { Name = name };
        }
    }

    internal class Manager : Employee
    {
        public string Name { get; set; }

        public override string GetProgressReport()
        {
            return "Manager overrides GetProgressReport";
        }
    }

従業員にはいくつかの焦点があります。

  1. 仮想関数非:GetyearsEmployed()、サブクラスに代わって上書きすることはできません。
  2. 仮想関数:GetProgressReport()、サブクラスを代表して無効にすることができます。
  3. 関数静的:Lookup()従業員の関数の代わりに別の従業員は、従業員のサブクラスであることができる、個々のリターンの代わりにリターンの種類を実行することことに注意することが、機能のこのタイプに固有である(そのようなものの例としてマネージャー)

マネージャは、に焦点を当てています。

  1. 従業員から継承されます。
  2. 上書きGetProgressReport():従業員の変数を呼び出すように宣言多型の概念によると、GetProgressReport()従業員やマネージャを提供します方法に、インスタンス変数の内容の種類に応じてクラスメソッドを呼び出すするかを決定します。

次の手順は、あなたが「の4章で参照することができ、オリジナルの友人を読んで興味を持っている実行時には、物事がどのように関連するかをちょうど私の理解の命令で行うためにここに、」。

ランタイムメモリ命令

まず、プログラムのコンテキストを見てみましょう。

        void M3(string[] args)
        {
            Employee e;
            Int32 year;
            e = new Manager();
            e = Employee.Lookup("Joe");
            year = e.GetYearsEmployed();
            e.GetProgressReport();
        }

次に、我々はプログラムの各行、そのメモリの使用について説明します。

作成したばかりのヒープ、M3の関数のスタックスレッド

画像

図4-6(C位を介してCLRから)

スタートは、ヒープとスレッドスタックは、実行したい、空であるM3()この機能aを。

M3()を呼び出すために準備ができて、従業員およびTypeオブジェクトのマネージャが初期化されます

画像

図4-7(C位を介してCLRから)

当 JIT 要将 M3() 方法转成 IL 时,会发现里面有使用到 Manager 与 Employee 这两个 type (当然还包括了 Int32 与 String 两个 type ,但这不是这边的重点,就不多做说明),接着 JIT 会确认参考到的 assemblies 有没有 load 进来这些 type 。

所以,这两个 type object 会被载入 heap 中。

每一个 type object 都会有 type object pointer 与 sync block index 两个 members ,如果该 type 有定义 static fields 的话,也会随着 type object 被载入到 heap 中。以下简单说明一下这几个东西:

  1. Type object pointer: 用来指向这个 instance ( 别忘了 type object 也是一种 instance ,它的 type 为 System.Type ) 的 type 位址
  2. Sync block index: 在 multi-thread 中用来控制同步的东西 (简单的说,可以让 multi-thread 透过它排队)
  3. Static field: 跟着 type object 的生命周期,因为每一个 type object 只会有一份,所以 static 可以拿来做 singleton, process 的全域变量等应用,相对的也要小心重入 (re-entry) 造成的问题。
  4. Method table: 定义这个 type object 中,拥有哪一些 method ,可以看到 Manager 的 type object 上,只有定义了 GenProgressReport 这个 override method ,而 Employee type object 则有定义 3 个 method 。

当 CLR 确定 M3() 要使用到的 type object 都已经准备好后,M3() 也已经经过 JIT 编译后, CLR 允许 thread 开始执行 M3 的 native code 。

开始执行 M3() ,初始化区域变量默认值

画像

Figure 4-8 ( from CLR via C# )

首先,先看到 M3() 中的第一行与第二行,声明了 2 个区域变量,分别为 e 与 year , CLR 会自动给这些区域变量默认值为 null 或 0 (reference type 为 null, value type 为 0)。

这时,还只有 stack 上有配置这 2 个区域变量, heap 还没被 reference 或被 M3() 使用到。

初始化对象的变化: e = new Manager();

画像

Figure 4-9 ( from CLR via C# )

接着透过 new operator 来调用 Manager 的构造函数,此时会回传 Manager object (执行个体)的位址,并存放在 e 的变量中,也就是在 stack 中, e 的内容是存放刚刚初始化的 manager object 在 heap 中的内存位址。

而就程序看起来,就只是把一个被初始化的 manager instance assign 给 e 这个变量。

简单记法: reference type 是在 stack 上存位址, value type 则是在 stack 上存内容

因此,可以看到 stack 上, e 的内容即关联到 heap 上,刚刚初始化完成的那个 manager object 。而因为这个 instance 的类型是 Manager ,所以这个 manager object 的 type object pointer 会指到 manager type object 的位置。

调用静态方法: e = Employee.Lookup("Joe");

画像

Figure 4-10 ( from CLR via C# )

接着,调用 Employee 上的静态方法: Lookup(String name) ,并将回传结果 assign 给 e 变化,这样会产生什么变化呢?

  1. 调用静态方法时, JIT compiler 会先找到这个静态方法的 type object ,然后从 type object 的 method table 中找到这个方法,将此方法内容即时编译(如果之前还没经过 JIT 编译过,才需要即时编译,若已经编译过,会有记录),执行编译后的内容。
  2. 这边的内容是初始化一个 manager 对象,将 Name 属性设为 Joe ,接着回传这个 Name 为 Joe 的 manager object 的位址,塞给 stack 中的 e 。

这时可以发现,原本上一行在 M3() 中初始化的 manager object 没有其他地方参考到它了,但是它仍会存在一段时间,等待 gc 起来之后,再依据 gc 的算法来回收这一个 heap 的内存。

到这边,e 这个区域变量,已经指到透过 Employee.Lookup("Joe") 所回传在 heap 中的 manager object 位址。

调用 non-virtual function: year = e.GetYearsEmployed();

画像

Figure 4-11 ( from CLR via C# )

当调用 e.GetYearsEmployed() 时,此方法并未被声明为 virtual (这边称为 non-virtual),也就代表不会有子类去覆写这个方法,所以 JIT compiler 会先找到这个变量的声明类型,也就是 e 的类型,在这边为 Employee 。

接着寻找 Employee 的 type object 中,是否存在着 GetYearsEmployed() 这个方法,若不存在,则 JIT complier 会一路往其父类找,默认最终会找到 Object 。

原因很简单,虽然变量类型声明为 Employee ,但是调用 e 这个执行个体的方法,这个方法可能是因为继承链上的父类拥有, Employee 上才能被调用。(例如任何类默认都继承 Object ,所以任何执行个体默认都可以调用 Object 的方法,如 ToString() ,若继承链上都没有其他 ToString() 的方法,那么最终 JIT compiler 就会调用 Object type object 上 method table 中的 ToString 方法。)

JIT compiler 能一路往父类寻找,是因为每一个 type object 上,有一个 field 指向 base type (图上没有标示),因此可以一路找到最原始的 Object 。

找到这个 non-virtual 的执行个体方法后,一样,若有需要 JIT compiler 会即时编译这个方法内容,然后执行 JIT 之后的程序。

以这例子来说,GetYearsEmployed() 会回传 5 ,5 是 int ,是 value type ,因此 5 这个值会被 assign 到 stack 上 year 的内容中。

调用 virtual function: e.GenProgressReport();

画像

Figure 4-12 ( from CLR via C# )

接着调用 Employee 上定义的 virtual function: GenProgressReport() ,当调用的是 virtual function 时,此时 JIT compiler 会额外产生一些 code 来处理,处理什么呢?

JIT compiler 会找到该变量 e 这个 instance 的执行个体类型为何,也就是透过在 stack 上 e 所存放的位址,找到了在 heap 中的执行个体,并透过 type object pointer 找到了 manager type object ,这个时候会寻找 manager type object 上的 method table ,确定是否有 GenProgressReport 这个方法,在这个例子中,因为 Manager 有 override GenProgressReport() ,因此 JIT compiler 找到后,会用同样的方式来执行 JIT 后的程序。

要注意的是,倘若 Employee.Lookup() 所回传的执行个体类型,若为 Employee 时,这边 JIT compiler 会找到的就应该是 Employee type object 上的 GenProgressReport 方法,而非 Manager type object 的方法。

书上虽没有提到,若 Manager 没有 override GenProgressReport() 的情况。不过我想,若 Manager type object 的 method table 找不到方法时, JIT 会用 non-virtual 的方式,往 base type 的 type object 一路寻找,直到 Object 的 type object 。
简单的说,也就是声明为 virtual 所影响的,是 JIT 会先找到 instance 的 type object,以此为起点,寻找 type object 或继承链上 type object 的 method table 。这样要找到实际的方法内容,会比 non-virtual 花的功夫更多。因为 non-virtual 是直接从声明的类型开始找,不必考虑 instance 的类型是否为子类的类型。

补充:Type Object 的 type object pointer 指到哪?

画像

Figure 4-13 ( from CLR via C# )

大家已经知道一般的 object 其 type object pointer ,就是指到该 instance 所对应的类型位置。那么, type object 的 type object pointer 又指去哪呢?答案是 System.Type 这个 type object ,也就是这些 type object 的类型,其实都是属于 System.Type 的一种。而最后 Type 的 type object ,其 type object pointer 则是指到自己身上。

说了这么多 type ,大家应该很容易联想到 System.Object 上所定义的 non-virtual instance method: GetType() 吧。

没错!每一个 instance 都可以调用 GetType() ,而这个方法定义在 System.Object 中,其内容就是回传 instance 的 type object pointer 所指到的 type object ,所以其回传类型为 System.Type ,因为 type object 的 type object pointer 指到 System.Type 的 type object 位置。

虽然像绕口令一样,不过了解了 heap 中 object 的关系后,也就没这么难懂了。

结论

CLR via C# 真的是一本不得不看的好书,这一篇文章其实翻译居多,只是鉴于这本书很多读者都因为一开始晦涩难懂而啃不下去,加上英文跟简体中文的内容描述,可能都不是很直觉,所以笔者在这边再反刍一次,也再强调一次,这一个段落真的说明了太多有趣的东西,是我看书之前不知道的,看完真的获益良多。

如果各位读者对于这个段落中的一些基本元素还不是很了解,建议务必要搞清楚,虽然不懂也可以写程序,但打开了这一扇窗,你会看到相当宽广的天空啊。

哪一些元素要知道,简单列出如下:

  1. stack 与 heap
  2. static 与 instance
  3. type 与 instance
  4. value type 与 reference type
  5. 继承
  6. 多态
  7. virtual 与 non-virtual
  8. JIT compiler 的角色与功能

我也是从这一个段落才理解了,什么叫做 static ,为什么称为 static ,虽然书中没有明讲,但了解了其 runtime 运行原理,自然会理解 static 这个命名的由来。


或许您会对下列培训课程感兴趣:

  1. 2019/7/27(六)~2019/7/28(日):演化式设计:测试驱动开发与持续重构 第六梯次(中国台北)
  2. 2019/8/16(五)~2019/8/18(日):【C#进阶设计-从重构学会高易用性与高弹性API设计】第二梯次(中国台北)
  3. 2019年9月21日(6)〜2019年9月22日(日):クリーンコーダー:DIとAOPの高度な戦闘二雁行(チャイニーズタイペイ)
  4. 2019年10月19日(F):[アートレガシーコードのユニットテストを追加]第エシェロン(台湾)
  5. 2019年10月20日(日):[開発]第八階段を高速化(チャイニーズタイペイ)

最初の手の情報公共のトレーニングコースを受けてみたい、または家のトレーニング、コンサルティング、コーチング、コンサルティングサービスについてお問い合わせしたいと思い、Facebookのファンページにお問い合わせください。道路の91アジャイル開発を。

オリジナル:大列  [読書体験]実行時型基礎のCを経由してCLRから #


おすすめ

転載: www.cnblogs.com/chinatrump/p/11458172.html