本系列为阅读《Effective C#》一书所做的笔记,内容基本来自于该书籍与网上相关文档与博客;
由于主要起笔记作用,文章只记录了部分书籍中提到的优化要点,一些不常用的、本人暂时无法理解的、已经掌握或自以为已经掌握的内容不再做记录;
内容上比较简要,要获取具体的内容需要查阅相关的资料。
GC
算法
Mark and Compact
循环引用的处理
每次回收过程中会从根节点开始遍历,不可达的对象可以直接视为一个整体清理掉,不需要每个对象记录自己的引用情况(引用计数),也避免了循环引用的问题
分代机制
每次回收过程,还有有效引用的对象会进入老一代,每老一代垃圾回收的频率会降低,以保证性能。
注意:如果有定义 finalizer ,对象被标记为垃圾时不会立即清理,而是等待 finalizer 调用后才进行;这样会导致该对象被标记时会进入老一代,以至于需要等待更多的回收周期才能将其真正清理掉;应考虑使用 IDisposable 接口(详见后续 IDisposable 节)
类成员变量初始化
示例
class MyObj {
private List<string> lst = new List<string>();
MyObj() {
// do sth.
}
}
初始化时机
类成员变量的初始化会在构造函数之前
避免重复初始化
初始化 0 或 null:
系统在执行开发者编写的代码之前会自动将整块内存初始化为 0,无需手动初始化
装箱拆箱操作:
public struct MyValType {
{
// elided
}
MyValType t = new MyValType();
手动初始化之前系统就会自动进行一次初始化,手动的初始化会触发 initobj 这条 il 指令来清零内存,导致装箱与拆箱的操作
不同构造函数的不同逻辑:
public class MyClass {
private List<string> lst = new List<string>();
MyClass() {
}
MyClass(int size) {
lst = new List<string>(size);
}
}
等同于:
public class MyClass {
private List<string> lst;
MyClass() {
lst = new List<string>();
}
MyClass(int size) {
lst = new List<string>();
lst = new List<string>(size);
}
}
相当于做了重复初始化
静态构造函数
定义及特性
refer to 静态构造函数(C# 编程指南)
静态构造函数用于初始化任何静态数据,或执行仅需执行一次的特定操作;在创建第一个实例或引用任何静态成员之前会自动调用静态构造函数
class SimpleClass {
// Static variable that must be initialized at run time.
static readonly long baseline;
// Static constructor is called at most one time, before any
// instance constructor is invoked or member is accessed.
static SimpleClass() {
baseline = DateTime.Now.Ticks;
}
}
静态构造函数具有以下属性:
- 静态构造函数不使用访问修饰符,不具有参数。
- 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数以初始化类。
- 不能直接调用静态构造函数。
- 用户无法控制在程序中执行静态构造函数的时间。
- 静态构造函数的一种典型用法是在类使用日志文件且将构造函数用于将条目写入到此文件中时使用。
- 静态构造函数对于创建非托管代码的包装类也非常有用,这种情况下构造函数可调用 LoadLibrary 方法。
- 如果静态构造函数引发异常,运行时将不会再次调用该函数,并且类型在程序运行所在的应用程序域的生存期内将保持未初始化。
注意:静态成员变量的初始化语句也会先于静态构造函数
实现单例模式
public class MyManager {
public static MyManager Instance {
get {
return instance;
}
}
private static MyManager instance;
// 在调用任何接口或成员前都会调用
static MyManager() {
instance = new MyManager();
}
// 初始化逻辑
MyManager() {
// do sth.
}
}
链式构造函数
示例
public class MyClass
{
private string name;
private int age;
public MyClass() : this("", 0) {}
public MyClass(string name, int age)
{
this.name = name;
this.age = age;
}
}
优势
- 省略重复代码
- 编译器可生成更高效的代码
IDisposable 接口
资源类型
C# 中资源分为两种类型:
- 托管资源(Managed):C# 中分配在堆上的资源,例如对象、数组等等,垃圾回收机制会在何时的时间进行回收
- 非托管资源(Unmanaged):常见的比如包装操作系统资源的对象,例如文件、网络连接、数据库连接等
释放非托管资源
一般情况下,为了保证非托管资源正确得到释放,具有非托管资源的对象必须实现 finalizer ,这样垃圾回收时会自动调用 finalizer 释放非托管资源;
然而具有 finalizer 的对象在回收时会有更多的开销,且会因为第一次无法清理(需等待 finalizer 执行后才进行清理)而进入老一代,导致回收时机变晚,故可以通过实现 IDisposable 接口(其实也可自己创建并实现其它接口,作用相同即可),并在合适的时机手动调用,以减少开销;
但即使实现了 IDisposable 接口,具有非托管资源的对象也还是必须实现 finalizer 来释放资源,避免 IDisposable 接口未能正常调用
标准实现方式
public abstract class ObjectBase : IDisposable {
private bool disposed = false;
// finalizer
~ObjectBase() {
Dispose(false);
}
// IDisposable
public void Dispose() {
Dispose(true);
// 如果手动调用 IDisposable 接口清理过资源,
// 则可让 GC 在回收时不必调用 finalizer
GC.SuppressFinalize(this);
}
// disposing:标记 Dispose 函数是由 IDisposable 接口调用还是 finalizer 调用
// 若是 IDisposable:同时清理托管和非托管资源
// 若是 finalizer:只需清理非托管资源,托管资源在垃圾回收时已被清理
private void Dispose(bool disposing) {
if (!disposed) {
if (disposing) {
DisposeManaged();
}
DisposeUnmanaged();
}
disposed = true;
}
// 释放托管资源(子类必须提供自己的实现来清理非托管资源)
protected abstract void DisposeManaged();
// 释放非托管资源
protected virtual void DisposeUnmanaged() {}
}