12 使用成员初始化器
运行时机
成员初始化器要比任何构造方法早运行。
何时不推荐
当不需要有默认值的时候
当要初始化为默认值的时候,不推荐还多余地做一个初始化。当不初始化的时候,系统直接把一整块内存置为0,如果自己画蛇添足偏要初始化,反而是多了个创建实例的消耗。1
2MyValType myVal; // 被初始化为0
MyValType myVal = new MyValType(); // 同样被初始化为0第一条直接把内存变为0,第二条则还要调用
initobj
这条IL命令。- 当还会在某个构造方法再次被初始化的时候
当然,不需要被初始化两次。
13 正确地初始化静态成员变量
静态构造方法。
1 | public class A{ |
- 静态构造方法由CLR自动调用
- 不能接受任何参数
- 必须处理任何可能会抛出的异常,否则(由于是CLR调用的)会使CLR终止整个程序
- 如果内部吞下了异常,会导致创建该类型的代码以失败告终,直到该AppDomain停止
16 避免创建非必要的对象
在那种频繁调用的方法中尽量避免创建对象(比如游戏中每帧调用的Update方法)。
创建过多对象会给GC造成负担。
两种方法可以改良:
- 把常用的局部变量升级为成员变量
- 提供一个类,存放某个类型常用实例的单例对象(比如某种颜色的笔刷)
17 实现标准的销毁模式
资源分为两种,一种称为非托管资源,可以理解为非c#对象,如系统API中的socket或者数据库连接;另一种成为托管资源,为c#对象,可由GC回收。
当使用非托管资源后,应该显示地调用非托管资源的关闭资源方法。
关闭一个资源常有三种方法:
- 析构方法(终结器) 特点是无法主动调用,且被调用时机不明,一般作为补救措施用
- 实现了IDisposable接口的Dispose方法
- Close方法(如Socket)
一般而言他们的关系是:InternalDispose方法为虚方法且不公开,执行释放资源的任务,Dispose方法公开且调用Dispose方法,在有些地方,Dispose方法也常被命名为Close,析构方法的实现亦同样是调用InternalDispose方法。
Dispose方法的逻辑中还需有一句命令,告知GC在回收本对象时无需再调用析构函数,避免二次调用InternalDispose。
从下面代码中可简明地明白他们的关系:
1 | public ASocketLikeClass: IDisposable |
应该注意的是,在InternalDispose中,不要写和释放资源无关的任何代码,避免造成对象的生命周期紊乱。
18 区分值类型和引用类型
c#中,struct为值类型,class为引用类型。值类型不支持多态,而引用类型支持。
值对象有更好的隐蔽性
当作为某个函数的返回值时,值类型会被复制一遍,而引用类型会被直接返回。故出现一问题,即如果函数返回了某个私有的引用类型对象,那么就相当于把该对象暴露出去,外界可以随意更改该对象。
1 | class A{ |
(代码1-1)
当PrivateClass是一个struct时,obj.xxx = 999
语句并不会影响到a._privateObject
的内容(因为修改的是它的副本),相反,当PrivateClass为一个class时,a._privateObject
的内容就可能会被改变。
内存分配
值类型被分配在栈上,引用类型被分配在堆上。
当你需要创建一个数组的时候,数组内容若是值类型,会更加高效(但是可能会爆栈)。
这是因为当创建该数组的时候,若,CLR将在栈上直接分配数组长度 * 值类型大小
那么大的内存空间。
而当内容为引用类型的时候,只会分配数组长度 * 引用指针大小
的内存于堆中。后续对数组的初始化过程中,会不断地在堆上new出新对象,它们在内存里的分布不一定是连续的,有可能会让堆上充满碎片,让程序变慢。
1 | struct A{ |
两者之间的转换
当想把struct转成class的时候要小心。由于struct作为值类型,根据前文所说,当它从方法中被返回出来后,外界的修改并不会影响内部数据,因而其修改非永久性的。若有人利用这一特性,而又有人把struct改成了class,就有可能导致数据被误修改。
参见代码1-1,当PrivateClass是个struct时,原有程序员可随意对其内容进行改动。若此时有人将struct草率地改成class,那么就会导致A._privateObject
的内容被永久改变。
19 保证0为值类型的初始状态
众所周知一个enum类型的值,默认值是0。但是如果你人为地让enum从1开始,不包括0,那么enum值被默认初始化成0之后,就成了一个非法值。
1 | class A |
为避免status为非法值,一般有两种处理方法:
利用默认初始化方法
1
private Status status = Status.StatusA;
给Status设置一个默认值0,如此,就会被CLR自动设置为None状态
1
2
3
4
5
6public enum Status
{
None = 0,
StatusA = 1,
StatusB = 2,
}