《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 ,如需转载请自行联系原作者
相关文章
《More Effective C# 》读书笔记 第一章
《More Effective C# 》读书笔记 第一章
|
存储 程序员 编译器
【Effective C++详细总结】第三章 资源管理
【Effective C++详细总结】第三章 资源管理
259 0
|
C++
读书笔记 effective c++ Item 32 确保public继承建立“is-a”模型
1. 何为public继承的”is-a”关系 在C++面向对象准则中最重要的准则是:public继承意味着“is-a”。记住这个准则。 如果你实现一个类D(derived)public继承自类B(base),你在告诉c++编译器(也在告诉代码阅读者),每个类型D的对象也是一个类型B的对象,反过来说是不对的。
867 0
|
Java C++
读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低
1. 牵一发而动全身 现在开始进入你的C++程序,你对你的类实现做了一个很小的改动。注意,不是接口,只是实现,而且是private部分。然后你需要rebuild你的程序,计算着这个build应该几秒钟就足够了。
845 0
|
安全 数据库 C++
读书笔记 effective c++ Item 29 为异常安全的代码而努力
异常安全在某种意义上来说就像怀孕。。。但是稍微想一想。在没有求婚之前我们不能真正的讨论生殖问题。 假设我们有一个表示GUI菜单的类,这个GUI菜单有背景图片。这个类将被使用在多线程环境中,所以需要mutex进行并发控制。
916 0
|
C++ 容器 存储
读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)
假设你正在操作一个Rectangle类。每个矩形可以通过左上角的点和右下角的点来表示。为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放入一个辅助结构体中,Rectangle中声明一个指向它的指针就可以了: 1 class...
961 0
|
C++ 数据安全/隐私保护
读书笔记 effective c++ Item 26 尽量推迟变量的定义
1. 定义变量会引发构造和析构开销 每当你定义一种类型的变量时:当控制流到达变量的定义点时,你引入了调用构造函数的开销,当离开变量的作用域之后,你引入了调用析构函数的开销。对未使用到的变量同样会产生开销,因此对这种定义要尽可能的避免。
828 0
|
C++
读书笔记 effective c++ Item 25 实现一个不抛出异常的swap
1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11)。Swap非常有用,恰当的实现swap是非常重要的,与重要性伴随而来的是一些并发症。
935 0
|
自然语言处理 数据安全/隐私保护 C++
读书笔记 effective c++ Item 22 将数据成员声明成private
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员。最后够得出结论:数据成员应该是private的。 1. 为什么数据成员不能是public的? 为什么数据成员不能够是public的? 2.1 一致性 让我们从句法的一致性开始(Item 18)。
893 0
|
C++
读书笔记 effective c++ Item 21 当你必须返回一个对象的时候,不要尝试返回引用
1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销。对纯净的按引用传递(不需要额外的构造或者析构)的追求丝毫没有懈怠,但他们的始终如一会产生致命的错误:它们开始传递指向并不存在的对象的引用。
958 0