C#中的数据类型

C#中的所有类型都是(直接或间接)从System.Object类型派生的。

C#的类型被分成两大类——引用类型(reference type),分配在内存堆上;值类型(value type),分配在堆栈上。如图:

数据类型.png

值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在退出作用域以前会释放资源。堆栈是从高地址往低地址分配内存。

引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存,如图:

引用类型.png

托管资源

托管资源指的是.NET可以自动进行回收的资源,被CLR控制的内存资源,这些资源的管理可以由CLR来控制,主要是指托管堆上分配的内存资源。

托管资源的回收工作是不需要人工干预的,由.NET运行库在合适调用垃圾回收器进行回收。

托管资源:从文字上看就是托付给别人管理,就像.NET的CLR,java的jvm。

.NET中超过80%的资源都是托管资源。

非托管资源

非托管资源指的是.NET不知道如何回收的资源,是CLR不能控制或者管理的部分,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等。

ApplicationContext, Brush, Component, ComponentDesigner, Container, Context, Cursor, FileStream, Font, Icon, Image, Matrix, Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI资源, 数据库连接等等资源,这些都是非托管资源

这些资源一般情况下不存在于托管堆中。垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源。但在.NET中,Object.Finalize()方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,所以对于包含非托管资源的类,可以将释放非托管资源的代码放在析构函数。

⚠️ 不能在析构函数中释放托管资源,因为析构函数是有垃圾回收器调用的,可能在析构函数调用之前,类包含的托管资源已经被回收了,从而导致无法预知的结果。

IDisposable接口

如果按照上面做法,非托管资源也能够由垃圾回收器进行回收,但是非托管资源一般是有限的,比较宝贵的,而垃圾回收器是由CRL自动调用的,这样就无法保证及时的释放掉非托管资源,因此定义了一个Dispose()方法,让使用者能够手动的释放非托管资源。

Dispose()方法释放类的托管资源和非托管资源,使用者手动调用此方法后,垃圾回收器不会对此类实例再次进行回收。

Dispose()方法是由使用者调用的,在调用时,类的托管资源和非托管资源肯定都未被回收,所以可以同时回收两种资源。

如果我们不想为一个类型实现Dispose方法,但我们想让它自动的释放非托管资源。那么就如上文所说,将释放非托管资源的代码放在析构函数。

Microsoft为非托管资源的回收专门定义了一个接口:IDisposable,接口中只包含一个Dispose()方法。任何包含非托管资源的类,都应该继承此接口。

在一个包含非托管资源的类中,关于资源释放的标准做法是:

1)继承IDisposable接口;

2)实现Dispose()方法,在其中释放托管资源和非托管资源,并将对象本身从垃圾回收器中移除(垃圾回收器不在回收此资源);

3)实现类析构函数,在其中释放非托管资源。

只要按照上面要求的步骤编写代码,该类就属于资源安全的类。

解释:在使用时,显示调用Dispose()方法,可以及时的释放资源,同时通过移除Finalize()方法的执行,提高了性能;如果没有显示调用Dispose()方法,垃圾回收器也可以通过析构函数来释放非托管资源,垃圾回收器本身就具有回收托管资源的功能,从而保证资源的正常释放,只不过由垃圾回收器回收会导致非托管资源的未及时释放的浪费。

⚠️在.NET中应该尽可能的少用析构函数释放资源。在没有析构函数的对象在垃圾处理器一次处理中从内存删除,但有析构函数的对象,需要两次,第一次调用析构函数,第二次删除对象。而且在析构函数中包含大量的释放资源代码,会降低垃圾回收器的工作效率,影响性能。所以对于包含非托管资源的对象,最好及时的调用Dispose()方法来回收资源,而不是依赖垃圾回收器。

附上MSDN的代码,大家可以参考。

public class BaseResource : IDisposable

{

// 指向外部非托管资源

private IntPtr handle;

// 此类使用的其它托管资源.

private Component Components;

// 跟踪是否调用.Dispose方法,标识位,控制垃圾收集器的行为

private bool disposed = false;

// 构造函数

public BaseResource()

{

// Insert appropriate constructor code here.

}

// 实现接口IDisposable.

// 不能声明为虚方法virtual.

// 子类不能重写这个方法.

public void Dispose()

{

Dispose(true);

// 离开终结队列Finalization queue

// 设置对象的阻止终结器代码

GC.SuppressFinalize(this);

}

// Dispose(bool disposing) 执行分两种不同的情况.

// 如果disposing 等于 true, 方法已经被调用

// 或者间接被用户代码调用. 托管和非托管的代码都能被释放

// 如果disposing 等于false, 方法已经被终结器 finalizer 从内部调用过,

// 你就不能在引用其他对象,只有非托管资源可以被释放。

protected virtual void Dispose(bool disposing)

{

// 检查Dispose 是否被调用过.

if (!this.disposed)

{

// 如果等于true, 释放所有托管和非托管资源

if (disposing)

{

// 释放托管资源.

Components.Dispose();

}

// 释放非托管资源,如果disposing为 false,

// 只会执行下面的代码.

CloseHandle(handle);

handle = IntPtr.Zero;

// 注意这里是非线程安全的.

// 在托管资源释放以后可以启动其它线程销毁对象,

// 但是在disposed标记设置为true前

// 如果线程安全是必须的,客户端必须实现。

}

disposed = true;

}

// 使用interop 调用方法

// 清除非托管资源.

[System.Runtime.InteropServices.DllImport("Kernel32")]

private extern static Boolean CloseHandle(IntPtr handle);

// 使用C# 析构函数来实现终结器代码

// 这个只在Dispose方法没被调用的前提下,才能调用执行。

// 如果你给基类终结的机会.

// 不要给子类提供析构函数.

~BaseResource()

{

// 不要重复创建清理的代码.

// 基于可靠性和可维护性考虑,调用Dispose(false) 是最佳的方式

Dispose(false);

}

// 允许你多次调用Dispose方法,

// 但是会抛出异常如果对象已经释放。

// 不论你什么时间处理对象都会核查对象的是否释放,

// check to see if it has been disposed.

public void DoSomething()

{

if (this.disposed)

{

thrownew ObjectDisposedException();

}

}

// 不要设置方法为virtual.

// 继承类不允许重写这个方法

public void Close()

{

// 无参数调用Dispose参数.

Dispose();

}

public static void Main()

{

// Insert code here to create

// and use a BaseResource object.

}

}

.NET Framework的System.GC类提供了控制Finalize的两个方法,ReRegisterForFinalize和SuppressFinalize。前者是请求系统完成对象的Finalize方法,后者是请求系统不要完成对象的Finalize方法。当你用Dispose方法释放未托管对象的时候,应该调用GC.SuppressFinalize。GC.SuppressFinalize会阻止GC调用Finalize方法。因为Finalize方法的调用会牺牲部分性能。如果你的Dispose方法已经对委托管资源作了清理,就没必要让GC再调用对象的Finalize方法(MSDN)。

析构函数只能由垃圾回收器调用。

Despose()方法只能由类的使用者调用。

在C#中,凡是继承了IDisposable接口的类,都可以使用using语句,从而在超出作用域后,让系统自动调用Dispose()方法。 一个资源安全的类,都实现了IDisposable接口和析构函数。提供手动释放资源和系统自动释放资源的双保险。