zoukankan      html  css  js  c++  java
  • clr via c#学习体会——clr如何调用一个类型中定义的方法(上)

        此篇博文是我最近看clr via c#的一些体会,可能有不对的地方,欢迎指正。

        我们写c#代码,将方法定义在一个类中,然后编译代码再执行,执行的时候,方法也好,字段也好,不管是实例方法/字段还是静态方法/字段,一定会在内存中的某处占用一定的内存空间。不管要访问某个字段,还是要调用某个方法,都必须知道字段或者方法在内存中的位置,这篇文章主要讨论的是clr如何获取到要调用方法在内存中的位置,从而进行方法调用的。

    一、类型对象

         首先我们先引入一个概念:类型对象,“类型对象”这个词并不是.net中的术语,只是我给它起的名字,所谓类型对象,是描述一个对象的类型的对象,这有点拗口,首先明确一点,类型对象也是一个对象,与咱们平时new object()得到的对象一样,在托管堆里分配内存,这个对象的作用是描述一个对象的类型。

         平时我们再写代码的时候,如果要查看某个对象的类型是什么,就会调用这个对象的GetType方法,来获取了一个Type类的子类(因为Type类是抽象类,所以GetType返回的一定是其子类),GetType方法返回的就是某个对象的类型对象。

         类型对象也是某个类型(Type类的子类)的实例,所以它会存在于托管堆中,类型对象并不是由程序员来实例化的,它的实例化由clr进行。在我们写代码的时候,必然会用到各种各样的类型,常用的int,string,bool,object,或者我们自己定义的结构体,类等,在程序运行时,clr会在第一次使用某个类型之前(比如实例化某个对象,或者调用某个类型的静态方法,静态字段等),实例化一个该类型的类型对象,并且该对象会常驻于内存中,直到所属的应用程序域(App Domain)被卸载,并且对于同一个类型,只实例化一个类型对象。想想也知道,因为某个类型的所有实例的类型都是一样的,所以只有一个类型对象是合理的。

    二、方法在内存中的位置在哪里

         在.net中,实例化一个对象(不管是值类型还是引用类型)的时候,只会为这个对象实例字段分配内存空间,不会为这个对象中定义的方法分配内存,实际上类型的所有实例共享相同的方法代码,对一个类型的不同实例而言,他们只是实例字段不相同。在程序运行时,对于每一个类型来说,该类型中定义的方法存在于内存中的某个位置,该类型的任意一个实例要调用某个方法的时候,有一种机制可以找到这个位置,从而执行该方法的代码。

         对于引用类型,在实例化的时候,会在堆上分配内存,除了为该对象的所有实例字段分配内存空间以外(一个类型的静态字段也是所有实例共享的,与方法相似),还会分配一些额外的空间,这些额外的空间用来存储对象的类型对象指针(Type object pointer)和同步块索引(sync block index )。对于值类型来说,实例化的时候不会有这两个额外的对象,但是当值类型被装箱的时候,会加上这两个额外对象。同步块索引是用于线程同步的,本文不做讨论,对象实例化以后再堆上的内存分配情况如下图。

    对象内存

        在这里我们说一下类型对象指针,从名字可以看出来类型对象指针是一个指针(这简直就是废话,如果不知道指针为何物,可以理解为引用),既然是一个指针,那就必然指向了内存中的某个位置,对于某个类型的所有实例来说,这些实例对象的类型对象指针都指向内存中的同一个位置,类型对象指针指向的这个位置,其实是另外一个类的实例,不过这个实例对象比较特殊,它是Type类的一个子类。在我们平时写代码的时候通过调用从object继承来的GetType()方法可以得到这个对象,对于值类型在调用GetType方法的时候,不是直接调用,会先对这个值类型实例进行装箱,然后再调用GetType方法,通过类型对象指针,可以获取某个对象的类型对象。在这个类型对象中保存了类型定义的所有静态字段,以及一个方法表,对于类型中定义的所有方法,不管是静态方法,实例非虚方法还是实例虚方法,都在方法表中有一条记录,这条记录包含了方法在内存中的位置信息通过方法表,clr可以知道运行时某个类型的某个方法在内存中的位置,从而可以让clr正确的调用方法。

          在这里有一点比较有趣的是,本文一开始就提到了,类型对象也是一个对象,与咱们实例化出来的其它对象一样,存在于托管堆中,那么它也拥有类型对象指针和同步块索引,它也有它自己的类型对象,所有类型对象的类型都是System.RuntimeType,所有的”类型对象“的“类型对象指针“都指向了内存中的同一个位置,不妨我们叫它根类型对象,根类型对象的类型对象指针是指向它自己的。就好比根类型对象就是.net中的上帝,它指定了一批人做教皇,并且赋予这些教皇权力来描述普通人的身份,一个普通人是男是女,家里几口人,人均几亩地,地里几头牛,都是教皇来描述的,并且同一类型的普通人由唯一的一个教皇来管理,而教皇的身份是由上帝来描述的,至于上帝,它自己说它是上帝,所以它就是上帝。 

    为了后面便于说明,我们定义如下两个类:

    class BaseClass
        {
            internal void SayHello()
            {
                Console.WriteLine("BaseClass say hello!");
            }
    
            internal virtual void PlayDota()
            {
                Console.WriteLine("BaseClass play dota,用的英雄是沙王");
            }
        }
    
     class DrivedClass:BaseClass
        {
            internal new void SayHello()
            {
                Console.WriteLine("DirvedClass say hello");
            }
    
            internal override void PlayDota()
            {
                Console.WriteLine("DrivedClass play dota,使用的英雄是幻影刺客");
            }
        }

    现在我们来实例化两个个BaseClass类的实例和两个DrivedClass类的实例


                BaseClass baseClass1=new BaseClass();
                BaseClass baseClass2=new BaseClass();

                DrivedClass drivedClass1=new DrivedClass();
                DrivedClass drivedClass2=new DrivedClass();

    对于这四个对象及其他们类型对象在内存中的情况如下图。

    类型对象指针

    为了验证前面说的上帝教皇普通人的问题,可通过以下代码验证:

    BaseClass baseClass1=new BaseClass();
    BaseClass baseClass2=new BaseClass();

    DrivedClass drivedClass1=new DrivedClass();
    DrivedClass drivedClass2=new DrivedClass();

    object o=new object();
    string s = "aaaaa";
    Type baseClass1Type = baseClass1.GetType();
    Type baseClass2Type = baseClass2.GetType();
    Type drivedClass1Type = drivedClass1.GetType();
    Type drivedClass2Type = drivedClass2.GetType();
    Type objectType = o.GetType();
    Type stringType = s.GetType();
    Console.WriteLine("baseClass1引用的对象类型为"+baseClass1Type.ToString());
    Console.WriteLine("drivedClass1引用的对象类型为" + drivedClass1Type.ToString());
    Console.WriteLine("baseClass2引用的对象类型为" + baseClass2Type.ToString());
    Console.WriteLine("drivedClass2引用的对象类型为" + drivedClass2Type.ToString());
    Console.WriteLine("o引用的对象类型为" + objectType.ToString());
    Console.WriteLine("s引用的对象类型为" + stringType.ToString());

    if (object.ReferenceEquals(baseClass1Type,baseClass2Type))
    {
         Console.WriteLine("baseClass1和baseClass2两个普通人是由同一个教皇描述的");
    }
    if (object.ReferenceEquals(drivedClass1Type,drivedClass2Type))
    {
          Console.WriteLine("drivedClass1和drivedClass2两个普通人是由同一个教皇描述的");
    }
    if (!object.ReferenceEquals(baseClass1Type,drivedClass1Type))
    {
           Console.WriteLine("baseClass1和drivedClass1两个普通人是由不同的教皇描述的");
    }

    Type baseClassTypeType = baseClass1Type.GetType();//获取类型BaseClass的类型对象的类型对象
    Type drivedClassTypeType = drivedClass1Type.GetType();//获取类型DrivedClass的类型对象的类型对象
    Type objectTypeType =objectType.GetType();//获取类型Object的类型对象的类型
    Type stringTypeType =stringType.GetType();//获取类型string的类型对象的类型
    Console.WriteLine("BaseClass的类型对象的类型是"+baseClassTypeType.ToString());
    Console.WriteLine("DrivedClass的类型对象的类型是"+drivedClassTypeType.ToString());
    Console.WriteLine("object的类型对象的类型对象是"+objectTypeType.ToString());
    Console.WriteLine("string的类型对象的类型对象是"+stringTypeType.ToString());

    if (object.ReferenceEquals(baseClassTypeType,drivedClassTypeType))
    {
           Console.WriteLine("管理BaseClass的教皇和管理DrivedClass的教皇都是归上帝管的");
    }

    if (object.ReferenceEquals(baseClassTypeType,objectTypeType))
    {
           Console.WriteLine("管理BaseClass的教皇和管理object的教皇都是归上帝管的");
    }

    if (object.ReferenceEquals(objectTypeType,stringTypeType))
    {
           Console.WriteLine("管理object的教皇和管理string的教皇都是归上帝管的");
    }

    if (object.ReferenceEquals(objectTypeType,objectTypeType.GetType()))
    {
          Console.WriteLine("上帝自己管自己,它说自己是上帝它就是");
    }

    控制台输出如下

    image

    讲到这里,我们大致明白了clr要调用一个方法的时候,需要先找到该方法所在的类型对象,然后从该类型对象的方法表中找到该方法在内存中的位置进行调用。但是c#作为一门面向对象的编程语言,其中有一些复杂的情况,具体的情况包括两种:方法重写(override)和方法隐藏(new)。对于这部分内容在下一篇博文中进行讨论。

  • 相关阅读:
    CodeForces 660D Number of Parallelograms
    【POJ 1082】 Calendar Game
    【POJ 2352】 Stars
    【POJ 2481】 Cows
    【POJ 1733】 Parity Game
    【NOI 2002】 银河英雄传说
    【NOI 2015】 程序自动分析
    【POJ 1704】 Georgia and Bob
    【HDU 2176】 取(m堆)石子游戏
    【SDOI 2016】 排列计数
  • 原文地址:https://www.cnblogs.com/onepiece_wang/p/2768938.html
Copyright © 2011-2022 走看看