《Effective C#》读书笔记——条目14:尽量减少重复的初始化逻辑<.NET资源管理>

简介:

构造函数的工作是为了初始化对象的所有成员,而一个类有多个构造函数又是一个非常常见的情景,所有这些构造函数难免会有类似乃至相同的逻辑,并且随着时间的推移,成员变量的增加,功能的改变,构造函数的个数也会不断上升。很多的开发人员一般会先编写一个构造函数,然后将其代码复制粘贴到其他的构造函数当中,以支持在类接口上定义的多个重写构造函数.其实我们不应该这样做,当发现多个构造函数包含类似的逻辑时,我们可以将其提取到一个公共的构造函数中。这样既可以避免代码重复也可以利用构造函数初始化器(constructor initializer)生成更高效的目标代码。

 

 阅读目录:

      1.构造函数直接的相互调用

      2.使用默认参数减少代码

      3.共有构造函数VS共有辅助方法

      4.CLR构造类型实例操作过程

      5.小节

      6.进一步阅读&参考

 

1.构造函数之间的相互调用

  构造函数初始化器允许一个构造函数去调用另一个构造函数。通过构造函数之间的相互调用可以有效减少重复代码,下面是一个构造函数之间相互调用的简单示例:

复制代码
 1 public class MyClass
 2 {
 3     private List<string> coll;
 4     private string name;
 5     
 6     public MyClass():this(0,"")
 7     {
 8     }
 9     
10     public MyClass(int initialCount):this(initialCount,string.Empty)
11     {
12     }
13     
14     public MyClass(int initialCount,string name)
15     {
16         coll=(initialCount>0)?new List<string>(initialCount):new List<string>();
17         this.name=name;
18     }
19 }
复制代码

 

2.使用默认参数减少重复代码

  我们还可以通过使用C# 4.0 的新特性——默认参数来进一步减少构造函数中的重复代码。我们可以将上面的代码所以的构造函数统一成一个,并为所有的可选参数指定默认值。如果将上面的代码想使用重载来穷举出同样多的功能那么至少需要提供四个构造函数:一个无参数,一个接受initialCount参数,一个接受name参数(调用时需要使用具名参数调用),一个同时接受initialCount参数和name参数。可以看到:

随着参数的增多,需要提供的重载也会直线上升,而使用默认参数可以有效减少构造函数的重复代码,这是一种避免过多重载的良好机制

复制代码
 1     public class MyClass
 2     {
 3         private List<string> coll;
 4         private string name;
 5         private string p;
 6 
 7         public MyClass()
 8             : this(0, string.Empty)
 9         {
10         }
11 
12         //构造函数使用了可选参数,这里name参数使用""而不是更具语义的Empty因为:Empty不是编译器常量,所以不能作为默认参数
13         public MyClass(int inititalCount = 0, string name = "")
14         {
15             coll = (inititalCount > 0) ? new List<string>(inititalCount) : new List<string>();
16             this.name = name;
17         }
18     }
复制代码

  使用默认参数还是提供多个重载的构造函数是一个值得权衡的问题(参见:Effective C# 读书笔记 条目10)。在上面的例子中,只需要后面使用可选参数的构造函数即可满足我们的要求,这里还保留一个无参构造函数是因为:使用了new()约束的泛型类不支持所以参数都有默认值的构造函数,为了满足new()约束,类必须提供显示的无参构造函数。

 

3.共有构造函数 VS共有辅助方法

   默认参数是C# 4.0的新特性,C#在4.0之前的版本中必须编写每个需要支持的构造函数。这意味着很多的重复代码,这时我们可以使用构造函数链,让一个构造函数调用声明在同一个类中的另一个构造函数,而不是像C++那也创建一个公有的辅助方法——因为创建公有的辅助方法会阻碍编译器对代码进行优化。我们看下面的代码(不好):

View Code

 

上面的类使用了一个构造函数公有的辅助方法,和上一个使用默认参数的示例类似,只不过:一个是构造函数间的调用,一个是使用公有的辅助方法。不过在编译时编译器会为使用辅助方法版本的示例中添加一系列的代码:即所有的成员初始化器(参见:Effective C# 读书笔记 条目12),并且还会调用基类的构造函数,所以这回使我们的代码效率大打折扣,并且当我们将name字段定义为readonly的时候会抛出编译错误:

 readonly 字段必须在声明或构造函数中初始化。

最后,我们应该知道创建共有构造函和提供共有的辅助方法数的区别在于:

编译器并不会生成多次调用基类构造函数的代码,也不会讲实例变量初始化器复制到每个构造函数中去。基类的构造函数会被最后一个构造函数调用一次:构造函数定义只能制定一个构造函数初始化器,要么使用this()委托给另一个构造函数,要么使用base()调用基类的构造函数,二者不可兼得。

 

4.CLR构造类型实例的过程

创建类型的第一个实例所执行的操作顺序图:

第二个以及之后的实例将直接从第五步开始,因为类的构造器仅执行一次,而且第六步第七步将被优化,以便构造函数初始化器使编译器移除重复的指令,执行顺序如下图:

 

5.小节

   使用C#的构造函数初始化器可以很好的将这些公有的逻辑抽取出来,只需编写一次,也只需要执行一次。到底是使用默认参数还是提供多个构造函数重载需要根据具体的使用场景来抉择,一般情况下应该使用为一个公有的构造函数使用默认参数,并且给出的默认参数值必须永远足够合理,并且不能抛出异常。同时我们需要保证在实例的构造过程中对每个成员变量仅初始化一次,而实现这一点最好的方法就是,尽可能早的进行初始化工作。使用初始化器来初始化简单资源,使用构造函数来初始化需要复杂逻辑的成员,同时将构造函数们的重复逻辑抽取到一个共有得构造函数中,以便减少重复代码

 

参考资料&进一步阅读

命名实参和可选实参

new约束

类型参数的约束

readonly(C# 参考)

本文转自gyzhao博客园博客,原文链接:http://www.cnblogs.com/IPrograming/archive/2012/11/23/EffectiveCSharp_14.html ,如需转载请自行联系原作者
相关文章
|
C# 开发者
C# 9.0中的模块初始化器:程序启动的新控制点
【1月更文挑战第14天】本文介绍了C# 9.0中引入的新特性——模块初始化器(Module initializers)。模块初始化器允许开发者在程序集加载时执行特定代码,为类型初始化提供了更细粒度的控制。文章详细阐述了模块初始化器的语法、用途以及与传统类型初始化器的区别,并通过示例代码展示了如何在实际项目中应用这一新特性。
C#中声明、初始化和实例化
C#中声明、初始化和实例化
262 0
|
自然语言处理 C# 数据安全/隐私保护
50.c#:string类初始化
50.c#:string类初始化
456 1
|
存储 C# 容器
掌握 C# 变量:在代码中声明、初始化和使用不同类型的综合指南
变量是用于存储数据值的容器。 在 C# 中,有不同类型的变量(用不同的关键字定义),例如: int - 存储整数(没有小数点的整数),如 123 或 -123 double - 存储浮点数,有小数点,如 19.99 或 -19.99 char - 存储单个字符,如 'a' 或 'B'。Char 值用单引号括起来 string - 存储文本,如 "Hello World"。String 值用双引号括起来 bool - 存储具有两个状态的值:true 或 false
284 2
《More Effective C# 》读书笔记 第一章
《More Effective C# 》读书笔记 第一章
173 0
|
存储 程序员 编译器
【Effective C++详细总结】第三章 资源管理
【Effective C++详细总结】第三章 资源管理
320 0
|
C++ 容器 数据库连接
读书笔记 effective c++ Item 8 不要让异常(exceptions)离开析构函数
1.为什么c++不喜欢析构函数抛出异常 C++并没有禁止析构函数出现异常,但是它肯定不鼓励这么做。这是有原因的,考虑下面的代码: 1 class Widget { 2 3 public: 4 5 .
918 0
|
C++ 编译器 安全
读书笔记 effective c++ Item 6 如果你不想使用编译器自动生成的函数,你需要明确拒绝
问题描述-阻止对象的拷贝   现实生活中的房产中介卖房子,一个服务于这个中介的软件系统很自然的会有一个表示要被销售的房屋的类: 1 class HomeForSale { ... };   每个房产中介会立刻指出来,要销售房屋的每个属性都是唯一的,没有两个完全一样的房屋。
907 0
|
C++ 容器 C语言
读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数
1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: 1 class TimeKeeper { 2 3 public: 4 5 TimeKeeper(); 6 7 ~TimeKeeper(); 8 9 .
1205 0

热门文章

最新文章