override members of ‘object’
ToString()- 设计规范
- 更多用于面向开发者的诊断信息
- 不用”” 代替 null
- 尽可能返回可以标识对象的信息
- 不要在ToString里造成副作用
int GetHashCode()- 重写Equals一定要重写GetHashCode()
- 将类作为哈希表集合的key时也应重写
- 重写原则
- a.Equals(b) ⇒ a.GetHashCode() = b.GetHashCode()
- 在对象的生存期内,其HashCode不应该改变。可以在某个地方缓存对象的HashCode
- 该方法不应引发异常,必须成功返回一个值
- 尽量唯一、在int范围内平均分布、开销小
- ValueType的GetHashCode方法调用大量反射,性能低
- [C# Essentials] Value Type
HashCode.Combine()方法可用于结合几个特征字段来生成符合要求的哈希码- 2 ~ 8个参数
public static int Combine<T1, T2>(T1 value1, T2 value2); public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3); public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4); // 其他重载...
Equals(object obj) - 要重写Equals也需要同时重写GetHashCode(), == 和 !=
- 具体原则
- 先判断null
- 判断类型是否相同(
GetType() == obj.GetType()) - sealed类直接用is匹配就行了,因为不存在继承问题,没必要使用GetType()
- 调用类型特定的
Equals(DataType obj)方法 - 判断引用相等 (
ReferenceEqual()) - 若HashCode的计算代价很小(例如cached)或不随属性改变,则比较HashCode
- 若基类重写了Equals方法则调用
关于哈希表集合 (
Dictionary<TKey, TValue>, HashSet<T> ) 中对对象的寻址方法:- 调用对象的
GetHashCode获得HashCode
- 对HashCode & 0x7FFFFFFF(取绝对值)模 集合大小,得到桶的位置
- 处理哈希冲突,无论采用链地址法(.NET Framework早期)还是开放寻址法(.NET Core / .NET 5+的优化做法),「桶」都是可遍历的。按照这个遍历顺序遍历每个元素,调用对象的
Equals方法进行「精准匹配」
operator override
public sealed class C public static bool operator == (C leftHandSide, C rightHandSide) { // ... // 不要在这里使用C类型的==,判断null可以用is } public static C operator + (C source, OtherType destination) { // ... // 实现二元操作符后,自动实现复合赋值操作符(如+=) } // 转换操作符 public static implicit operator double(C c) { // 向double类型的转换 // explicit则是显式转换 }
类型封装
对一个类型添加访问修饰符以控制其可访问范围
可用的是internal和public,默认为internal
成员的可访问性无法大于所属类的
即internal class的public member无法被其他程序集访问
垃圾回收
.NET的垃圾回收
mark-and-compact算法:
- 一次GC周期开始时,识别对象的所有根引用
- 根引用:来自static变量、CPU寄存器、局部变量或参数实例、弱引用对象的引用
- 基于根引用列表,遍历每个根引用所标识的树形结构,递归确定所有根引用指向的对象。识别出所有的可达对象 (mark)
GC时,用可达对象覆盖不可达对象所占用的内存(compact,如同磁盘碎片清理)
垃圾回收期运行期间,进程的所有托管线程会暂停,造成短暂卡顿(一般不明显)
可用
System.GC.Collect()手动GC,在需要执行一些 不希望卡顿的 不太消耗内存的 方法前可以调用越近产生的垃圾会被优先(更快的频率)回收
弱引用
System.WeakReference<T> 可以用于创建「不阻止GC对其进行回收的对象」优点就是不会一直占用内存,在内存压力较大时可以先给它回收了;缺点是如果被回收了之后需要再次重建,存在一定的性能开销
public static class ByteArrayDataSource { static private byte[] LoadData() { byte[] data = new byte[1000]; // load it from somewhere return data; } static private WeakReference<Byte[]> Data { get; set; } static public byte[] GetData() { byte[]? target; if (Data is null) { target = LoadData(); // 注:一定别直接用方法返回值作为构造函数的参数, // 有可能还没赋给Data就被回收了= = Data = new WeakReference<byte[]>(target); return target; } } else if (Data.TryGetTarget(out target)) { return target; } else { // 弱引用被GC了,重新创建 target = LoadData(); Data.SetTarget(target); return target; } }
常用API
构造函数:
WeakReference<T>(T target)- 作用:创建一个弱引用,指向指定的目标对象。
bool TryGetTarget(out T target)- 作用:尝试获取弱引用指向的目标对象。如果对象未被垃圾回收,则返回
true并将对象赋值给target;否则返回false。
void SetTarget(T target)- 作用:设置弱引用指向一个新的目标对象。
bool IsAlive - 作用:获取弱引用指向的对象是否仍然存活(未被垃圾回收)。
- 注意:即使
IsAlive为true,在调用TryGetTarget时对象仍可能被回收(存在竞态条件)。
不确定性终结
~ClassName(){...} 由垃圾回收器在自动运行(不能显式调用)
正常来说在最后一次使用后,程序结束前调用一次,但也有可能一次都不调用
用于清理和内存无关的资源,如删除文件什么的
由于Finalizer在自己的线程中执行,不确定性终结的未处理异常很难诊断,故一定要防止Finalizer中产生的异常逃逸
确定性终结
继承
IDisposable 接口,重写Dispose() 方法,这样可以被显式调用建议在末尾执行
System.GC.suppressFinalize() ,从终结(f-reachable)队列中移除这个类这个f-reachable队列的对象处于一个准备被终结并GC的状态,由专门的一个线程根据执行上下文,挑选合适实践进行处理
这会造成托管资源的GC时间推迟