【原创】《.net本质论》读书笔记三

摘要:
CLR中只有三种值类型:基本数据类型,如Int32和Boolean;结构;枚举引用类型继承自系统对象。运行时内存结构包含对象头、记录虚拟函数和其他信息,并存储在托管堆上。引用类型不同。等价意味着对象的内部值相等,同一性意味着引用是否指向同一对象。可以通过根引用直接或间接找到的对象是可访问的,其他对象是不可访问的。这个物体将延续到下一代。

上一章讲的是Type,这一章讲了实例在内存中的情况。主要内容有两个——分清楚值类型和引用类型;搞清楚GC是怎么回事。这其中当然也介绍了一些有用的函数了。

 

1、值类型和引用类型

值类型是继承于System.ValueType的类型,它与引用类型一样可以有字段、方法,但是不能被继承,运行时是存储在栈上的。

 

值类型在CLR中只有三种:基本数据类型,如Int32,Boolean;struct;enum。

 

引用类型则是继承于System.Object的,其运行时的内存结构中有对象头,记录虚函数等信息,并是存储在托管堆上的。

 

当编译器遇到c#中的int、bool等原始类型时,会将它们翻译成CLR的Int32、Boolean类型,在运行时,这些Int32、Boolean不像引用类型的实例,有对象头。虽然System.ValueType也是继承于System.Object,但是ValueType这个类就告诉了编译器,要将这些类型区别对待。

【原创】《.net本质论》读书笔记三第1张【原创】《.net本质论》读书笔记三第2张一个值类型的定义
struct Oil
    {
        int price;
        public Oil(int p){this.price = p;}
    }

代码中,Oil和price都是值类型,看中间代码:

image

可以看出,Oil是继承于ValueType的,(值得注意的是:c#代码中不可以写:ValueType来表示继承,当写struct时,就是隐式继承于ValueType,而且编译器会隐式地加上sealed关键字),price也被翻译成Int32了。

 

enum(枚举)也是一个值类型, enum默认是Int32类型,而且如果没有指定显式值的话,默认从0,1,2这样开始。enum的类型也可以指定类型,如下:

可以用enum实现bitmask(位掩码),如下:

用[System.Flags]属性时,enum底层的ToString()方法也会改变。如下:

【原创】《.net本质论》读书笔记三第4张【原创】《.net本质论》读书笔记三第5张使用Flags属性的enum
static void Main(string[] args)
        {
            CarOptions car = CarOptions.FogLights | CarOptions.Spoiler | CarOptions.SunRoof;
            Console.WriteLine(car.ToString());
        }

        [System.Flags]
        public enum CarOptions
        {
            SunRoof = 0x01,
            Spoiler = 0x02,
            FogLights = 0x04,
            TintedWindows = 0x08,
        }

CarOptions里的值分别为1、2、4、8,从二进制角度看就是一个位值,所以叫位掩码。上面这个代码输出了ToString()是:

image

这时,(int)car的值是7.

 

总结值类型和引用类型的不同:

a、分配内存地方不同,值类型在stack上,引用类型所指对象在managed heap上。

b、值类型是继承于ValueType的,后者不是。

c、二者都可以有构造函数并通过new来调用。不同在,前者的new被编译器翻译为iniobj,后者被翻译为newobj,而且后者声明有参ctor后不可以有默认ctor,前者可以。

d、前者只能实现接口,不能继承类,后者可以实现接口、继承类。

 

函数传参时是值传递,那如果struct里有一个引用类型呢?这时传入的还是struct的值,不过当改变struct里引用类型对象的field时,这时做改变的是托管堆上的内容,所以,传入值看似也变了,其实还是值传递。

 

2、相等(equivalent)和同一(identical)

对于值类型,判断相等和同一是一样的,只是指的比较。引用类型则不同,equivalent是说对象内部值相等,identical是说引用是否指向同一个对象。判断两个引用是否指向同一个对象,用Object.ReferenceEquals(Object o), 也可以用==(在c++或c#里)。但是,由于运算符可以重载,所以不建议用==判断。如果使用Object.GetHashCode()的返回值来判断,两个对象的hashcode不相等,则肯定不是一个引用;如果相等,也不一定是一个对象。因为不同对象通过hash函数也可以得出相同hash值。

 

对于equivalent,需要自定义类型重载Object的Equals(Object o),才能正确判断对象是否相等。

 

一般来说,Object.GetHashCode(), Object.Equals(), IComparable.CompareTo()是需要同时override的。

 

3、拷贝

从一个对象拷贝出另一个对象,用Object.MemberwiseClone(), 这个是shallow copy(即值拷贝)。要实现对象的deep copy,需要实现ICloneable接口,覆写Clone()函数。String类的Clone也是shallow copy,用String.Copy(string str)可以新创建实例。

 

4、boxing、unBoxing

装箱操作发生在把一个值类型赋给一个引用,拆箱操作发生在把一个对象转换为值类型。注意:int到Int32是编译器映射的,不算是装箱。

 

5、多维数组

CLR支持两种多维数组——一种是c类型的数组,每行的元素个数相等;一种是交错(jagged)数组,每行的元素个数可以不等。以二维为例:

第一种的声明方式: int[,] array = new [3,4]; // 三行两列

第二种的声明方式: int[][] array = new [3][]; //三行,每行个数不定

第一种可以通过array.GetLength(int dimension)获取每一维的长度,start with 0;

第二种需要array[0] = new int[8]来定义每一行。

 

6、关于GC

CLR的GC通过查找所有引用,以此识别哪些对象是不可以回收的,其余的托管内存就可以回收。但实际上没有那么简单。

 

a、根引用vs非根引用

     根引用是static引用或者local variable,非根引用是对象的字段。能通过根引用直接或间接找到的是reachable的对象,其他是unreachable的。词法上的存活期不代表这个对象就是reachable的,CLR通过JIT编译器的liveness信息来判断一个local variable是否是存活的。

 

b、当确定了哪些对象是unreachable的,GC就可以回收这些内存。至于GC何时、如何回收,程序员无需关心。

 

c、终结处理(Object Finalization)

如果想在对象被回收时写清理代码,可以重写Finalize()函数。不过这个函数在c#中不可以直接写,而是用析构函数来表示的。编译器会在Finalize方法里用析构函数的代码,最后调用基类型的Finalize方法。

这个过程是异步的,什么意思呢?当GC开始回收时,发现这个对象虽然是unreachable但是有Finalize方法,这时GC不会立即回收这个对象,而是将它挂到FinalizeQueue上(就是这个queue有一个referece指向对象)等待GC的终结线程来执行这个方法。这样这个对象会survive到下一个generation。这导致回收线程和终结线程不同时发生,甚至相隔时间较大,异步产生。

 

使用Finalize方法会让对象存活时间变长,而且会占用两次回收过程,不建议使用,在实际编程中,应该用Dispose方法。

 

d、Dispose()

实现了IDispose()接口,通过程序员主动调用Dispose()进行资源清理,并在Dispose()函数里写System.GC.SuppressFinalize(this)的方法,不让对象挂到FinalizeQueue上,这样,对象是即时被回收的,而且没有进入下一个generation,同时也节省了一个回收过程。

 

利用using语法,在对象离开using范围时,Dispose自动被调用,即时清理资源。如下:

【原创】《.net本质论》读书笔记三第7张【原创】《.net本质论》读书笔记三第8张using语法自动调用Dispose方法
using(T obj1 = new T(), obj2 = new T())
{
   //obj1、obj2是local variables
}  //obj1/obj2.Dispose被调用

e、弱引用(weakReference)

我们上面说的都是强引用,就是说有引用直接指向对象。GC不会回收强引用指向的对象。那么,当一个对象我们需要访问,但是如果GC想回收也可以回收的时候,就要用到弱引用System.WeakReference。举例说 :

【原创】《.net本质论》读书笔记三第9张【原创】《.net本质论》读书笔记三第10张弱引用的使用
            Truck t = new Truck(200);
            System.WeakReference weak = new WeakReference(t, false);//创建弱引用
            t = null;//删除强引用
            t = (Truck)weak.Target;//利用弱引用的target找回强引用
            Console.WriteLine(t);

            t = null;//再次删除强引用
            System.GC.Collect();//垃圾回收

            t = (Truck)weak.Target;//试图利用弱引用的target找回强引用
            if (t == null)//run获取不到,但是debug时可以获取
                Console.WriteLine("reference collected");
            else
                Console.WriteLine(t);

上面代码详细介绍了弱引用的使用。但是有一个问题,当GC回收后,我run代码输出

image

debug时输出:

image

这是为什么呢?

免责声明:文章转载自《【原创】《.net本质论》读书笔记三》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Redis--模糊查询(转)Python -- 限流 throttle下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Python标准库存储对象(pickle包,cPickle包)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢! 在之前对Python对象的介绍中 (面向对象的基本概念,面向对象的进一步拓展),我提到过Python“一切皆对象”的哲学,在Python中,无论是变量还是函数,都是一个对象。当Python运行时,对象存储在内存中,随时等待系统的调用。然而...

4. Go语言—值类型和引用类型

一、值类型 1. 定义 ​ 变量直接存储的值,内存通常在栈中分配; var i = 5 -> i-->5 2. 应用 int、float、bool、string、数组、struct 二、引用类型 1. 定义 ​ 变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,通...

Corona SDK新手教程:tap、touch和multitouch的区别

本教程主要讲解一下tap、touch和multitouch的区别,以及如何处理每种方式。 如果是新手的话,可以先复习一下之前的文章 CoronaSDK之交互系统和事件检测 1 Tap检测 Tap事件是用户和触屏之间交互最基础的一种。本质上来说,一个tap就是表示用户用手指接触到屏幕,然后在差不多附近的位置再抬起的过程。这个tap事件只有在用户在同一个点接触...

Object.freeze

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。数据冻结后不再被修改在vue中数据冻结的...

【c#基础】泛型

1:减少代码的选项可以是用Object类,也可以使用泛型 但是Object类进行类型传递不是类型安全的。泛型类使用泛型类型保证了类型安全。 泛型类泛型方法泛型接口、结构、委托 泛型优点: 1:性能 装箱和拆箱很容易使用,但性能损失比较大。 泛型的话在编译的时候就会动态生成我们定义他的类型,这样就不用进行装箱和拆箱。 var list=new List<...

QTP的基本功能介绍

• QTP的基本功能介绍 HP QuickTest Professional 支持功能測试和回归測试自己主动化,用于每一个主要软件应用程序和环境。此解决方式使用keyword驱动的測试概念,简化了測试创建和维护过程。它使測试人员可以使用专业的捕获技术直接从应用程序屏幕中捕获流程来构建測试案例。測试专家还可通过集成的脚本和调试环境全然訪问内在測试和对象属...