上一节介绍到了如何进行Object方法重写和操作符重载,本篇博客来接着介绍合式类型剩余的内容:
其中一些相对简单的内容就不进行过多的介绍了,例如引用其他程序集,只需要注意三种引用方式:
注意库和包并非只能由控制台程序引用。事实上,任何程序集都能引用其他任何程序集。经常是一个库引用另一个库,创建一个依赖链。同时可以关注下类型的访问级别:
需要注意:成员的可访问性不能大于其包容类,类(除了嵌套类,其可使用任意修饰符)只能使用public和internal。命名空间比较简单,只需要注意如下设计规范就行了:
总而言之就是,最好一个类在一个文件,用一个命名空间包裹一个类。
虽然C#编译器在最终生成的可执行文件中忽略所有注释,但开发者可利用命令行选项,指示编译器将XML注释提取到单独的XML文件中。这样就可根据XML注释生成API文档。此外,C#编辑器可解析代码中的XML注释,并对其进行分区显示(例如,可使用有别于其他代码的一种颜色),或者解析XML注释数据元素并向开发者显示。这样就可以进行文档规范化操作,例如可以定义一些参数,生成API文档的时候予以解析
垃圾回收是“运行时”的核心功能,旨在回收不再被引用的对象所占用的内存。这句话的重点是**“内存”和“引用”。垃圾回收器只回收内存,不处理其他资源,比如数据库连接、句柄(文件、窗口等)、网络端口以及硬件设备(比如串口)。此外,垃圾回收器根据是否存在任何引用来决定要清理什么。这暗示垃圾回收器处理的是引用对象,只回收堆上的内存。另外,还意味着假如维持对一个对象的引用,就会阻止垃圾回收器重用对象所用的内存。**
.Net使用mark-and-compact算法,就是先把可达对象整理和覆盖不可访问对象内存,然后执行清理,整个操作流程如下,使用可达性算法进行:
相较于Java的成体系的垃圾回收算法,感觉CLR做的不是很好,也许是没有体系化的了解过吧。
弱引用是什么?弱引用就是一直维持的一个引用,但是它并不会组织垃圾回收,为什么要有弱引用呢?这么去想象一下,假如要从数据库加载一个特别大的对象,如果是强引用,一旦用户不再使用该对象,断开引用就需要进行垃圾回收,但假如用户一会儿又想用了,又得重新加载和引用,但如果是弱引用,你可以在保持引用的状态下进行垃圾回收,但在你垃圾回收之前我还是一直保持引用状态,那么假如用户下次请求的时候刚好CLR还没有回收对象,就可以直接获取到对象了,实际上相当于一次内存里的缓存。
public class Program
{
private WeakReference Data;
public FileStream GetData()
{
FileStream data = (FileStream)Data.Target;
if(data != null)
{
return data;
}
else
{
// Load data
// ...
// Create a weak reference
// to data for use later
Data.Target = data;
}
return data;
}
public static void Main()
{
Console.WriteLine("No output in this example.");
}
}
创建弱引用(Data)之后,可查看弱引用是否为null来检查垃圾回收。但这里的关键是先将弱引用赋给一个强引用(FileStream data=Data),避免在“检查null值”和“访问数据”这两个动作之间,垃圾回收器运行并清除弱引用。强引用明显会阻止垃圾回收器清除对象,所以它必须先被赋值(而不是先检查Target是不是为null)
我们知道垃圾回收器不负责处理除了堆上的引用资源,那么除了内存管理外的一些数据库连接以及句柄等这些资源该怎么释放呢,这个时候就要用到终结器,同时终结器在垃圾回收前也发挥着不可替代的作用(虽然会降低性能,但是能保证对象被延迟清除):
class TemporaryFileStream
{
public TemporaryFileStream(string fileName)
{
File = new FileInfo(fileName);
Stream = new FileStream(
File.FullName, FileMode.OpenOrCreate,
FileAccess.ReadWrite);
}
public TemporaryFileStream()
: this(Path.GetTempFileName())
{
}
// Finalizer
~TemporaryFileStream()
{
Close();
}
public FileStream Stream {
get; }
public FileInfo File {
get; }
public void Close()
{
Stream?.Dispose();
File?.Delete();
}
}
}
上边这个终结器用来删除文件和关闭流。需要注意的几点是:
这里的dispose一个对象不是垃圾回收一个对象,而是释放该对象中包装的资源,例如它字段所引用的对象,解除引用,具体的垃圾回收还是需要垃圾回收器的。这里有个漏洞会导致Dispose()执行不了,也就是在实例化TemporaryFileStream后,调用Dispose()前如果发生异常可能不会调用Dispose(),这个时候就要使用确定性终结:
public static void Search()
{
using(TemporaryFileStream fileStream1 =
new TemporaryFileStream(),
fileStream2 = new TemporaryFileStream())
{
// Use temporary file stream;
}
}
相当于包裹了一个try finally块。所以实现了终结器的方法的对象的垃圾回收流程是这样的(如果终结器不调用System.GC.SuppressFinalize):整体流程是:先由可达性分析算法把待清理的对象引用放到f-reachable队列里,然后由终结器进行引用终结,终结完成后垃圾回收器才对对象进行垃圾回收。需要注意以下几点:
可以看到一个对象被回收会经历多么复杂的过程,那么我们最好让他在需要出现的时候再初始化岂不更好:
using AddisonWesley.Michaelis.EssentialCSharp.Chapter10.Listing10_21;
class DataCache
{
// ...
public TemporaryFileStream FileStream
{
get
{
if (_FileStream == null)
{
_FileStream = new TemporaryFileStream();
}
return _FileStream;
}
}
private TemporaryFileStream _FileStream = null;
// ...
}
只有调用FileStream属性的get方法时才加载TemporaryFileStream。
总结一下,其实也就是程序集如何加载,命名空间使用规范,垃圾回收如何执行以及终结器(析构函数),它的好处和缺点。