【C#本质论 十一】合式类型(二)程序集引用、XML注释、垃圾回收和资源清理

标签: C#学习系列

上一节介绍到了如何进行Object方法重写和操作符重载,本篇博客来接着介绍合式类型剩余的内容:
在这里插入图片描述

程序集引用及命名空间定义

其中一些相对简单的内容就不进行过多的介绍了,例如引用其他程序集,只需要注意三种引用方式:

  • 第一种方式是引用库项目文件,指出库的源代码在哪个项目中,并在两个项目之间建立依赖关系。编译好库之后才能编译引用了该库的程序。该依赖关系造成在编译程序时先编译库(如果还没有编译的话)项目拷贝,常用
  • 第二种方式是引用程序集文件本身。换言之,引用编译好的库而不是项目。如果库和程序分开编译,比如由企业内的另一个团队编译,这种方式就非常合理。dll拷贝,调试时候用)
  • 第三种方式是引用NuGet包项目拷贝高级版,从NuGet库拷贝项目

注意库和包并非只能由控制台程序引用。事实上,任何程序集都能引用其他任何程序集。经常是一个库引用另一个库,创建一个依赖链。同时可以关注下类型的访问级别:
在这里插入图片描述
需要注意:成员的可访问性不能大于其包容类类(除了嵌套类,其可使用任意修饰符)只能使用public和internal。命名空间比较简单,只需要注意如下设计规范就行了:

  • 为命名空间附加公司名前缀,防止不同公司使用同一个名称。
  • 要为命名空间二级名称使用稳定的、不随版本升级而变化的产品名称
  • 不要定义没有明确放到一个命名空间中的类型。
  • 考虑创建和命名空间层次结构匹配的文件夹结构

总而言之就是,最好一个类在一个文件,用一个命名空间包裹一个类。

XML注释

虽然C#编译器在最终生成的可执行文件中忽略所有注释,但开发者可利用命令行选项,指示编译器将XML注释提取到单独的XML文件中。这样就可根据XML注释生成API文档。此外,C#编辑器可解析代码中的XML注释,并对其进行分区显示(例如,可使用有别于其他代码的一种颜色),或者解析XML注释数据元素并向开发者显示。这样就可以进行文档规范化操作,例如可以定义一些参数,生成API文档的时候予以解析
在这里插入图片描述

垃圾回收

垃圾回收是“运行时”的核心功能,旨在回收不再被引用的对象所占用的内存。这句话的重点是**“内存”和“引用”垃圾回收器只回收内存,不处理其他资源,比如数据库连接、句柄(文件、窗口等)、网络端口以及硬件设备(比如串口)。此外,垃圾回收器根据是否存在任何引用来决定要清理什么。这暗示垃圾回收器处理的是引用对象,只回收堆上的内存。另外,还意味着假如维持对一个对象的引用,就会阻止垃圾回收器重用对象所用的内存。**

垃圾回收算法

.Net使用mark-and-compact算法,就是先把可达对象整理和覆盖不可访问对象内存,然后执行清理,整个操作流程如下,使用可达性算法进行:

  1. 一次垃圾回收周期开始时,识别对象的所有根引用。根引用是来自静态变量、CPU寄存器以及局部变量或参数实例(包括f-reachable对象)的任何引用基于该列表,垃圾回收器可遍历每个根引用所标识的树形结构,并递归确定所有根引用指向的对象。这样,垃圾回收器就可识别出所有可达对象
  2. 执行垃圾回收时,垃圾回收器不是枚举所有访问不到的对象;相反,它将所有可达对象紧挨着放到一起,从而覆盖不可访问的对象(也就是垃圾,或者不可达对象)所占用的内存,为定位和移动所有可达对象,系统要在垃圾回收器运行期间维持状态的一致性。为此,进程中的所有托管线程都会在垃圾回收期间暂停。这显然会造成应用程序出现短暂的停顿。不过,除非垃圾回收周期特别长,否则这个停顿是不太引人注意的。为尽量避免在不恰当的时间执行垃圾回收,System.GC对象包含一个**Collect()**方法。可在执行关键代码之前调用它(执行这些代码时不希望GC运行)。这样做不会阻止垃圾回收器运行,但会显著减小它运行的可能性——前提是关键代码执行期间不会发生内存被大量消耗的情况
  3. 发现相较于长期存在的对象,最近创建的对象更有可能需要垃圾回收。为此,.NET垃圾回收器支持“代”(generation)的概念,它会以更快的频率尝试清除生存时间较短的对象(新生对象)。而那些已在一次垃圾回收中“存活”下来的对象(老对象)会以较低的频率清除。具体地说,共有3代对象。一个对象每次在一个垃圾回收周期中存活下来,它都会移动到下一代,直至最终移动到第二代(从第零代开始)。相较于第二代对象,垃圾回收器会以更快的频率对第零代的对象执行垃圾回收

相较于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队列里,然后由终结器进行引用终结,终结完成后垃圾回收器才对对象进行垃圾回收。需要注意以下几点:

  • 要只为使用了稀缺或昂贵资源的对象实现终结器方法,即使终结会推迟垃圾回收。
  • 要为有终结器的类实现IDisposable接口以支持确定性终结
  • 要为实现了IDisposable的类实现终结器方法,以防Dispose()没有被显式调用。
  • 要重构终结器方法来调用与IDisposable相同的代码,可能就是调用一下Dispose()方法。
  • 不要在终结器方法中抛出异常
  • 要从Dispose()中调用System.GC.SuppressFinalize(),使垃圾回收更快地发生,并避免重复性的资源清理
  • 要保证Dispose()可以重入(可被多次调用)。
  • 要保持Dispose()的简单性,把重点放在终结所要求的资源清理上。
  • 避免为自己拥有的、带终结器的对象调用Dispose()。相反,依赖终结队列清理实例。
  • 避免在终结方法中引用未被终结的其他对象(对象复活,在f-reachable队列中又被外部引用了)。
  • 要在重写Dispose()时调用基类的实现。
  • 考虑在调用Dispose()后将对象状态设为不可用。对象被dispose之后,调用除Dispose()之外的方法应引发ObjectDisposedException.异常。(Dispose()应该能多次调用。)
  • 要为含有可dispose字段(或属性)的类型实现IDisposable接口,并dispose这些字段引用的对象

可以看到一个对象被回收会经历多么复杂的过程,那么我们最好让他在需要出现的时候再初始化岂不更好:

 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。

总结一下,其实也就是程序集如何加载,命名空间使用规范,垃圾回收如何执行以及终结器(析构函数),它的好处和缺点。

版权声明:本文为sinat_33087001原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_33087001/article/details/103844442

智能推荐

C#本质论读书笔记分享<一>

重读Csharp本质论一书,发现上面已经记录1500多条注释笔记,整理一下分享出来,里面会有对笔记的复制,也有一些新的思考。 PS:该书真是神书,除了基础语法以外,还讲解了“为什么”“如何做”这种问题,让读者知其然,也知其所以然。每个章节的内容深入浅出,覆盖面较广,有时还会提醒开发者,应该养成什么样的编码风格,无论是初学者还是有一定Csharp基础的...

ASP.net本质论之用控制台应用程序创建Asp.net服务器

主题 概要 Asp.net 应用程序域、HttpRunTime 编辑 时间 新建 20170925 序号 参考资料 1 Asp.net本质论 2 C#高级编程(第七版) 3 http://blog.csdn.net/sh524555685/article/details/7454244(应用程序域解释) 4 http://blog.csdn.net/lhc1105/article/details/...

TensorFlow架构与设计:OP本质论

作者:刘光聪 ,中兴通讯高级系统架构师,专注机器学习算法,分布式系统架构与优化。 原文:TensorFlow架构与设计:OP本质论 责编:王艺 CSDN AI记者,投稿、寻求报道、深入交流请邮件[email protected]或扫描文末二维码添加微信。 相关文章: 图解TensorFlow架构与设计 TensorFlow架构与设计:图模块 TensorFlow架构与设计:会话生命周期 TensorF...

asp.net本质论基础篇 get请求与post请求

get post请求 1.首先建个文件夹 将建的东西都放在一个文件夹下 (1)建html (2)建个一般处理程序 (3)在建一个一般处理程序 总结内容 学asp.net本质论 get post 后面会经常用,暂时理解一下,不需要刻意记...

C#垃圾回收和资源管理

计算机内存有限,当变量或对象不在需要内存的时候,必须回收内存。值类型离开作用域就会被销毁,引用类型呢? … … 对象生存期 上面的代码中new表面上是单步操作,但实际分两步走: 1.new从堆中分配原始内存,这个阶段无法干预 2.new操作符将原始内存转换成对象,这时必须初始化对象。这个阶段可用构造器控制. monkey变量离开作用域时,它引用的Monkey对象就没人引...

猜你喜欢

C++引用的本质

1.什么叫引用:引用就是给变量起别名 2.引用的语法:数据类型& 别名=原名  3.引用的本质是常量指针,所以引用必须初始化,且初始化后不能修改。 4.引用作为函数参数以及函数返回值  调试结果:...

冒泡排序,及改进方式,性能优化400%>>>附图解加源码

首先源码附上,源码中带有注释,看不懂没关系,源码后面附带图解,最后附上代码效率提升图 源码如下: 方案一:其实实现很简单,两层循环,每次内层迭代出最大的一个值,将其放入数组最后一个位置,外层循环的末端便往前移一位。其原理如下图 方案一代码块: 方案二:优化改进 仔细观察上面的图,我们不难发现当迭代到图下这样的情况时,其实已经全排序好了,但是我们还是需要对它进行1,2,3,4,5,6的迭代,这些情况...

css3常用选择器

先介绍一下基本选择器,如下几种 *:通配符。选择页面的所有元素 E:元素选择器。对页面的一些元素进行选择,如p,div,li等 .class:类选择器 #id:id选择器。一个Id在一个页面中只能选择,这是和类选择器的区别 E F:后代选择器。如ul li。选中ul下的li E>F:子元素选择器。如下,可以用 h1 > strong {color:red;}使h1的子元素strong变...

python is not set from command line or npm configuration

问题 猜测原因  python版本冲突 解决方案  去官网找到最新版本python 点击下载,注意记住你下载的目录,下载好以后,打开这个目录下的文件夹,Python310, 整个文件夹复制一下 在项目里运行npm config list --json 找到python的路径   我的是 C:\\Python27\\python.exe 说明项目里用的还是py...

Java8 Stream流操作

前言 我们常常需要将一个容器转化成另一个容器,或是对这个容器中的数据进行批量处理,这时使用Stream流可以大大减少我们的工作量。 1 Stream概述 Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。 那么什么是Stream? Stream将要处理的元素集合看作一种流,在流的过程中,借助St...