《Effective C#》快速笔记(二)- .NET 资源托管

简介

  续 《Effective C#》读书笔记(一)- C# 语言习惯

  .NET 中,GC 会帮助我们管理内存,我们并不需要去担心内存泄漏,资源分配和指针初始化等问题。不过,它也并非万能,因为非托管资源需要我们自己进行清理,如文件句柄、数据库连接、GDI+ 对象和COM 对象等。

目录

  • 十二、推荐使用成员初始化器而不是赋值语句
  • 十三、正确地初始化静态成员变量
  • 十四、尽量减少重复的初始化逻辑
  • 十五、使用 using 和 try/finally 清理资源
  • 十六、避免创建非必要的对象
  • 十七、实现标准的销毁模式
  • 十八、区分值类型和引用类型
  • 十九、保证 0 为值类型的有效状态
  • 二十、保证值类型的常量性和原子性

十二、推荐使用成员初始化器而不是赋值语句

  1.成员初始化器:在声明变量时就进行初始化,而不是在每个构造函数中进行。

  2.以下 3 种情况,应避免使用成员初始化器:

  (1)当你想要初始化对象为 0 或 null 时。因为系统默认的初始化工作(在所有代码执行前)会将一切设置为 0 或 null,我们做的是一步多余的操作。而且,如果是值类型,那么性能非常差。

            MyValueType myVal1; //初始化为 0
MyValueType myVal2 = new MyValueType(); //也是 0

  这两条语句都将变量初始化为 0,但第一条是通过设置包含 myVal1 的这一块内存为 0 实现的,而第二条使用的是 initobj 这条 IL 指令,导致了对 myVal2 变量的一次装拆箱操作,这将占用额外性能与时间。

  (2)需要对同一个变量执行不同的初始化方式:

    class Program
{
/// <summary>
/// 声明并初始化
/// </summary>
private List<string> _lables = new List<string>(); public Program() { } public Program(int capacity)
{
_lables = new List<string>(capacity);
}
}

  初始化该类时,假如使用的是带 capacity 的构造函数,那么 List<string> 对象表示初始化了 2 次,头一个就成为了垃圾对象。

  (3)将初始化代码放在构造函数的合适理由:可以方便异常管理 try-catch。

十三、正确地初始化静态成员变量

  1.在使用类型的实例之前,就应该初始化该类型的所有静态成员变量。静态构造函数是一个特殊的函数,将在其他所有方法、变量或属性被第一次访问之前执行。你可以使用这个函数来初始化静态变量和实现单例模式等操作。

  2.静态初始化器和静态构造函数是初始化类的静态成员的最佳选择。

  3.使用静态构造函数而不是静态初始化器最常见的理由是可以捕捉和处理异常。

十四、尽量减少重复的初始化逻辑

  1.如果多个构造函数包含类似的逻辑,我们应将其提取到一个公共的构造函数中,这样可以避免代码重复,也可以利用构造函数初始化器生成更高效的代码。

    class MyClass
{
private List<string> _lables;
private string _name; public MyClass() : this(, string.Empty)
{ } public MyClass(int capacity = , string name = "")
{
_lables = new List<string>(capacity);
_name = name;
}
}

  第二个构造函数使用了 "" 来给出 name 的默认值,而不是采用 string.Empty ,因为 string.Empty 并不是一个编译期的常量,而是一个定义在 string 类中的静态属性,所以不能用作参数的默认值。

  2.创建某个类型的第一个实例时所进行的操作顺序:

  (1)静态变量设置为 0 ;

  (2)执行静态变量初始化器;

  (3)执行基类的静态构造函数;

  (4)执行静态构造函数;

  (5)实例变量设置为 0;

  (6)执行实例变量初始化器;

  (7)执行基类中合适的实例构造函数;

  (8)执行实例构造函数。

  3.使用初始化器来初始化简单的资源,使用构造函数来初始化需要复杂逻辑的成员,同事不要忘记将调用抽取到一个构造函数中,以便减少重复。

  4.构造函数定义中只能使用一个初始化器,要么使用 This() 委托给另一个构造函数,要么使用 base() 调用基类的构造函数。

十五、使用 using 和 try/finally 清理资源

  1.使用了非托管系统资源的类型必须显示地使用 IDisposable 接口的 Dispose() 来释放,Using() 语句将生成一个Try/finally 块。

十六、避免创建非必要的对象

  1.GC 可以很好地管理内存,但不管多高效,分配和销毁堆上的对象总会花费很长时间,如果过多的创建引用对象,那么会对程序的性能产生严重的影响。

        public void Paint()
{
using (var myFont = new Font("Arial", 10.0f))
{
Console.WriteLine($"使用 {myFont} 进行绘画");
}
}

  假如该方法被非常频繁地调用。每次调用时都会创建另一个 Font 对象,但它包含的内容和之前的是完全一样。GC 每次都要为你清理这些垃圾,显然是非常低效的。

  可以把 myFont 提升为静态变量。

        private readonly static Font _myFont = new Font("Arial", 10.0f);

        public void Paint()
{
Console.WriteLine($"使用 {_myFont} 进行绘画");
}

  2.降低程序中创建对象数量的方法。

  (1)将常用的局部变量提升为成员变量;

  (2)提供一个类,存放某个类型常用实例的单例对象。

  3.用 StringBuilder 进行复杂的字符串操作

十七、实现标准的销毁模式

  1.IDisposable.Dispose() 方法的实现中需要完成如下 4 个任务:

  (1)释放所有非托管资源;

  (2)释放所有托管资源,包括释放事件监听程序;

  (3)设定一个状态标志,表示该对象已经被销毁;

  (4)跳过终结操作,调用 GC.SuppressFinalize(this) 即可。

十八、区分值类型和引用类型

  1.一般来说,我们创建的大部分是引用类型。

  2.确定创建值类型的条件有 4 个

  (1)该类型的主要职责在于数据存储;

  (2)该类型的公有接口都是由访问其数据成员属性定义的吗?

  (3)你确定该类型绝不会有派生类型吗?

  (4)你确定该类型永远都不需要多态支持吗?

  3.用值类型表示底层存储数据的类型,用引用类型来封装程序的行为。

  4.如果你对类型未来的用途不确定,应选择引用类型。

十九、保证 0 为值类型的有效状态

  1..NET 系统的默认初始化过程会将所有的对象设置为 0,建议将 0 作为枚举类型的默认值。

  2.枚举(enum)必须将 0 设定为枚举值的一个有效选择。所有的枚举值都派生自 System.ValueType。枚举的默认值开始于 0。

  3.在创建自定义枚举值时,请确保 0 是一个有效的选项。若你定义的是标识(flag),那么可将 0 定义为没有选中任何的标志。

    enum Week
{
None = ,
Monday = ,
Tuesday = ,
Wednesday = ,
Thursday = ,
Friday = ,
Saturday = ,
Sunday =
}

二十、保证值类型的常量性和原子性

  1.常量性:自创建后,其值保持不变。因为不能更改内部状态,就可以省去许多不必要的错误检查,它也是线程安全的,也可以安全地暴露给外界,因为调用者不能改变对象的内部状态。

  2.设计常量类型时,要确保没有任何漏洞会导致内部状态被外界更改。因为值类型不能派生,所以不必担心会受到派生类影响。

  不过,如果常量中是可变的引用类型字段的话,我们就应该对这些可变类型进行防御性的复制。

    class MyClass
{
private readonly object[] _objs; public MyClass(ICollection<object> objs)
{
_objs = new object[objs.Count];
objs.CopyTo(_objs, );  //复制
} public IEnumerable<object> Objs => _objs;
}
        static void Main(string[] args)
{
var objs = new object[];
var myClass = new MyClass(objs);
objs[] = "hi"; Console.WriteLine(myClass.Objs.ToArray()[]); Console.Read();
}

  因为数组是引用类型,如果不使用 CopyTo 复制一个副本的话,在外部的 objs 修改就会直接影响 MyClass 中的 _objs,因为他们指向的都是同一个引用。

  2.不要盲目地对每一个属性都加上 { get; set; }。

本系列

  《Effective C#》快速笔记(一)- C# 语言习惯

  《Effective C#》快速笔记(二)- .NET 资源托管

  《Effective C#》快速笔记(三)- 使用 C# 表达设计

  《Effective C#》快速笔记(四) - 使用框架

  《Effective C#》快速笔记(五) - C# 中的动态编程

  《Effective C#》快速笔记(六) - C# 高效编程要点补充


【博主】反骨仔

【原文】http://www.cnblogs.com/liqingwen/p/6761409.html

[.NET] 《Effective C#》快速笔记(二)- .NET 资源托管的更多相关文章

  1. C#学习笔记二 (资源托管,泛型,数组和元组,运算符和类型强制转换)

     托管和非托管资源 1.托管资源是指GC管理的内存空间,非托管资源是指文件句柄,网络连接,数据库连接等. 2.方法中临时申请的变量,被存放在栈中.栈存储非对象成员的值数据.例如在方法中有B b=new ...

  2. webpack笔记二 管理资源

    webpack笔记二 管理资源 webpack最出色的功能之一就是除了引入JavaScript,还可以通过loader引入任何其它类型的文件. 加载CSS 为了在JavaScript模块中import ...

  3. 《Effective C#》快速笔记(二)- .NET 资源托管

    简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内存,我们并不需要去担心内存泄漏,资源分配和指针初始化等问题.不过,它也并非万能 ...

  4. [.NET] 《Effective C#》读书笔记(二)- .NET 资源托管

    <Effective C#>读书笔记(二)- .NET 资源托管 简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内 ...

  5. [.NET] 《Effective C#》快速笔记(三)- 使用 C# 表达设计

    <Effective C#>快速笔记(三)- 使用 C# 表达设计 目录 二十一.限制类型的可见性 二十二.通过定义并实现接口替代继承 二十三.理解接口方法和虚方法的区别 二十四.用委托实 ...

  6. [.NET] 《Effective C#》快速笔记(四)- 使用框架

    <Effective C#>快速笔记(四)- 使用框架 .NET 是一个类库,你了解的越多,自己需要编写的代码就越少. 目录 三十.使用重写而不是事件处理函数 三十一.使用 ICompar ...

  7. [.NET] 《Effective C#》快速笔记 - C# 中的动态编程

    <Effective C#>快速笔记 - C# 中的动态编程 静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作.C# 是一种静 ...

  8. [.NET] 《Effective C#》快速笔记 - C# 高效编程要点补充

    <Effective C#>快速笔记 - C# 高效编程要点补充 目录 四十五.尽量减少装箱拆箱 四十六.为应用程序创建专门的异常类 四十七.使用强异常安全保证 四十八.尽量使用安全的代码 ...

  9. [.NET] 《Effective C#》快速笔记(一)- C# 语言习惯

    <Effective C#>快速笔记(一)- C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 ...

随机推荐

  1. prometheus — nginx-vts-exporter

    参考文档: https://blog.51cto.com/xujpxm/2080146 注: 本文留用自己参考,建议看以上参考文档,更为细致 prometheus 监控 nginx 使用 nginx- ...

  2. [LeetCode] Lemonade Change 买柠檬找零

    At a lemonade stand, each lemonade costs $5.  Customers are standing in a queue to buy from you, and ...

  3. NFS 系统搭建 - 成功

    NFS是Network File System的缩写,即文件系统.客户端通过挂载的方式将NFS服务器端共享的数据目录挂载到本地目录下. 工作流程 1.由程序在NFS客户端发起存取文件的请求,客户端本地 ...

  4. mysql 主从设置

    方法: 1.主服务器建立二进制日志,每产生语句或磁盘变化,写进日志 2.从服务器建立 relaylog日志 3.主服务器授权复制账号 4.从服务器利用复制账号来监听主服务器的日志 5.注意:所以的my ...

  5. 解决 Visual Studio 点击添加引用无反应的问题

    如遇到vs2010 点击添加引用无反应,主要是因为windows系统推送的更新问题,把windows系统推送的更新都更新一遍就好了. 如果已经安装VS后 Windows系统推荐的更新.360推荐的安全 ...

  6. SQL Server Service Broker创建单个数据库会话(消息队列)

    概述 SQL Server Service Broker 用来创建用于交换消息的会话.消息在目标和发起方这两个端点之间进行交换.消息用于传输数据和触发消息收到时的处理过程.目标和发起方既可以在同一数据 ...

  7. 【安富莱】【RL-TCPnet网络教程】第11章 RL-TCPnet调试方法

    第11章      RL-TCPnet调试方法 本章节为大家讲解RL-TCPnet的调试方法,RL-TCPnet的调试功能其实就是通过串口打印实时监控运行状态.而且RL-TCPnet的调试设置比较简单 ...

  8. Nginx限流配置

    电商平台营销时候,经常会碰到的大流量问题,除了做流量分流处理,可能还要做用户黑白名单.信誉分析,进而根据用户ip信誉权重做相应的流量拦截.限制流量.Nginx自身有的请求限制模块ngx_http_li ...

  9. Linux 设置自启动服务

     0 为停机,机器关闭.(千万不要把initdefault设置为0 )    1 为单用户模式,就像Win9x下的安全模式类似.    2 为多用户模式,但是没有NFS支持.    3 为完整的多用户 ...

  10. python连接Linux命令行

    #!/usr/bin/python # -*- coding: utf-8 -*- '''https://www.ibm.com/developerworks/cn/linux/l-cn-pexpec ...