C#
九、良构
- well-formed
9.1 重写object的成员
9.1.1 重写ToString()
- 默认情况下,调用对象的ToString()方法会返回类的完全限定名称
- 对于类似string类型来说,object的ToString()方法没有意义
9.1.2 重写GetHashCode()
- 原因
- 重写Equals()时就要重写GetHashCode()
- 类作为散列表集合的键,例如Hashtable和Dictionary,也应重写GetHashCode()
- 默认情况下,返回的是内存地址对应的HashCode
9.1.3 重写Equals()
- 默认调用object的ReferenceEquals()静态方法
- 引用的相等性并不是唯一“相等性”
- 重写Equals()的步骤
- 检查是否为null
- 如果是引用类型,检查引用是否相等
- 不相等,则继续比较
- 相等,则返回true
- 检查数据类型是否相同
- 密封类可以跳过
- 一个指定了具体类型的辅助方法,接收参数为同一类型,而不是与object类型进行比较
- 检查散列码是否相等
- 考虑性能可以跳过
- 如果基类重写了Equals(),就检查base.Equals()
- 比较每一个标识字段
- 重写GetHashCode()
- 重写==和!=操作符
public struct Longitude
{
//...
}
public struct Latitude
{
//...
}
public struct Coordinate
{
public Coordinate(Longitude longitude, Latitude latitude)
{
Longitude = longitude;
Latitude = latitude;
}
public Longitude Longitude {get;}
public Latitude Latitude {get;}
public override bool Equals(object obj)
{
if(obj==null) return false;
if(this.GetType() != obj.GetType()) return false;
return Equals((Coordinate)obj);
}
public bool Equals(Coordinate obj)
{
return ( (Longitude.Equals(obj.Longitude))&&
(Latitude.Equals(obj.Latitude)) );
}
public override int GetHashCode()
{
int hashCode = Longitude.GetHashCode();
hashCode ^= Latitude.GetHashCode();
return hashCode;
}
}
- 要一起实现GetHashCode()、Equals()、= =操作符和!=操作符
9.2 操作符重载
- 除非目的是使类型表现得像是一种基元类型(如数值类型),否则就不要去重载操作符
9.2.1 操作符重载的方式
- 在类型中定义public static bool operator ==(class A, class B)
9.2.2 规范
- 重载==操作符时,应避免在其实现中使用= =,否则会造成无限递归死循环
public sealed class ProductSerialNumber
{
public static bool operator ==(ProductSerialNumber leftHandSide, ProductSerialNumber rightHandSide)
{
//避免使用 leftHandSide == null 进行判断
if(ReferenceEquals(leftHandSide,null))
{
return ReferenceEquals(rightHandSide,null);
}
return (leftHandSide.Equals(rightHandSide));
}
public static bool operator !=(ProductSerialNumber leftHandSide, ProductSerialNumber rightHandSide)
{
return !(leftHandSide==rightHandSide);
}
}
- 若重载了二元操作符,则会自动重载二元操作符和赋值操作符的结合
- 例如,只重载了加号操作符,则编译器会自动重载 += 操作符
- 可以重载 true/false 操作符,使类型可以在if、do、while和for语句的控制表达式中使用
9.3 引用其他程序集
9.3.1 程序集目标
- 控制台可执行程序
- 类库
- Windows可执行程序
- 在操作系统中运行,不是使用控制台界面
- 模块
- 在程序集中集成多种语言,多个模块可以合并为程序集
9.3.2 可移植类库
- PCL ( portable class library)
- 可以跨平台使用的类库
9.3.3 类访问修饰符
- internal
- 修饰类,只能在程序集内部访问,默认是internal
- public
- 修饰类,可在程序集外部访问;同时,可以用internal修饰成员,使程序集外部无法访问该成员。
9.3.4 类成员访问修饰符
- protected
- 可以在其他程序集中新建类,并派生自本程序集,说明本程序集中的类是用public修饰的
- 但用protected修饰的成员,在其他程序集中只能在派生类中访问。
- protected internal
- 相当于protected和internal取并集,修饰成员
9.4 命名空间别名限定符
- 作用
- 解决命名空间或类名冲突的问题
- 来自不同的程序集的命名空间或类可能发生重名冲突
- 解决命名空间或类名冲突的问题
- extern alias 别名
- 在命令行中为程序集取别名,并在文件中通过extern alias引入别名
- 导入命名空间时使用别名::或者 .
- 例如,将程序集CoordinatesPlus.dll 命名为CoordPlus
- 通过CoordPlus::AddisonWesley.Michaelia.EssentialCSharp或者CoordPlus.AddisonWesley.Michaelia.EssentialCSharp引入CoordinatesPlus.dll程序集中的命名空间EssentialCSharp
- global
- 在全局命名空间中查找类型时使用,global::
9.5 XML注释
-
作用
- 提示
- 提取到单独的文件中生成相应的API文档
- 编译器提取注释,对代码进行分区显示(使用不同的颜色)
- 编译器提供帮助信息
-
单行XML注释
- ///
-
多行注释
- /** **/
9.6 垃圾回收
- 只回收堆上不再被引用的内存
9.6.1 .net垃圾回收器
- 从根引用出发遍历所有可访问的对象并把它们移动到一起以覆盖不可访问的对象
- 回收期间所有托管代码都会暂停
- 可在关键代码之前调用Collect()方法降低垃圾回收运行的概率
9.6.2 弱引用
- System.WeakReference 类型
- 可被垃圾回收
- 强引用会阻止垃圾回收
- 相当于对象的内存缓存
- 例如,一个很大的对象列表要从数据库中加载,可在垃圾回收之前重复使用该列表
9.7 资源清理
9.7.1 终结器 ~
- 特点
- 不能从代码显式调用,只能由垃圾回收器负责调用
- 不允许传递任何参数
- 不能确定确切的执行时间
- 作用
- 释放数据库连接等资源,不清理内存,内存是垃圾回收器清理
- 释放资源的备用机制,在主动释放没生效时作为最后手段
- 具有终结器的对象会进入f-reachable队列中,引用被持有,使对象在调用终结器之前存活一段时间而不进行垃圾回收
- 主动释放时可以使对象不加入f-reachable队列,即显式调用System.GC.SuppressFinalize(),从而加快垃圾回收的速度
- 主动释放的方式是实现IDisposable接口
- 规范
- 只为使用了稀缺或昂贵资源的对象实现终结器方法
class Program
{
static void Search()
{
TemporaryFileStream fileStream = new TemporaryFileStream();
//主动释放
fileStream.Dispose();
}
}
class TemporaryFileStream : IDisposable
{
public TemporaryFileStream(string fileName)
{
File = new FileInfo(fileName);
Stream = new FileStream(File.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
}
public FileStream Stream {get;}
public FileInfo File {get;}
~TemporaryFileStream(){
Dispose(false);
}
public void Dispose(){
Dispose(true);
System.GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
if(disposing)
{
Stream?.Close();
}
File?Close();
}
}
9.7.2 使用using语句进行确定性终结
- using语句中执行可能出异常的代码,结束时会隐式调用Dispose()方法进行资源清理
- 等同于try / finally
- 不会捕获异常
9.8 延迟初始化
- 在需要的时候才初始化对象
- 方式一
- 表达式主体用于只读属性、空接合操作符
public Person person => personA??(personA=new Person());
public Person personA {get;set;}=null;
- 表达式主体用于只读属性、空接合操作符
- 方式二
- System.Lazy<T>以及Lambda表达式
- public Person person => personA.value;
- public Lazy<Person> personA {get;} = new Lazy<Person>( ()=>new Person() );
- Lambda表达式为将来发生的事情提供指令,即()=>new Person()
- System.Lazy<T>以及Lambda表达式
- 区别:System.Lazy<T>线程安全