[转载]肥兔读书笔记之Effective C#(第2版) 第一章

[转载]肥兔读书笔记之Effective C#(第2版) 第一章 – 懒惰的肥兔 – 博客园.

Effective C#(第2版)中文名称为: C#高效编程 改进C#代码的50个行之有效的办法(第2版)

这本书的中文名字起的很蛋疼,其它Effective系列的书名都是Effective  XXX,在网上商城输入Effective就能全找到,唯独这本死活找不到,后来偶然机会才知到原来中文名称叫做C#高效编程 改进C#代码的50个行之有效的办法,真是蛋疼至极。

第一章 C#语言习惯

条目1 使用属性而不是可访问的数据成员
条目2 用运行时常量(readonly)而不是编译期常量(const)
条目3 推荐使用 is 或 as 操作符而不是强制类型转换
条目4 使用 Conditional 特性而不是#if 条件编译
条目5 为类型提供 ToString() 方法
条目6 理解几个等同性判断之间的关系
条目7 理解 GetHashCode() 的陷阱
条目8 推荐使用查询语法而不是循环
条目9 避免在API中使用转换操作符
条目10 使用可选参数减少方法重载的数量
条目11 理解短小方法的优势
小结

条目1 使用属性而不是可访问的数据成员

关于这个概念是程序员都知道,知道的原因更多是因为编程用属性已经成为了一种习惯,你写代码若不用属性会遭人鄙视,属性能更好的进行封装、更好 的进行权限访问控制等,然鲜为人知的是虽然属性和数据成员在源代码层次上完全兼容,但在二进制层面上却大相径庭。这也就意味着,若将某个公有的数据成员改 成了与之等同的公有属性,那么就必须重新编译所有用到了该公有数据成员的代码,否则运行时会抛出异常。

如下所示,若Customer类被其它程序或类进行了引用,当把Name从变量改为属性的时候必须重新编译所有引用了Customer类的代码,否则运行时会发生错误,虽然我们看上去没任何区别,但将公有变量改为属性破坏了二进制兼容性,从而造成更新单一程序集变得很困难,认识这点没啥太大的实际意义,但是可以让我们更加坚定使用属性的信念。

public class Customer
{
    ///
<summary> /// 这里声明为公有的变量
 /// </summary>
    public string Name;
}
public class Customer
{
    ///
<summary> /// 这里声明为公有的属性
 /// </summary>
    public string Name { get; set; }
}

条目2 用运行时常量(readonly)而不是编译期常量(const)

C#有两种类型的常量:编译期常量运行时常量。二者有着截然不同的行为,使用不当可能会带来很严重的的后果,而使用者还茫然不知所措。

编译期常量使用const关键字声明,运行时常量使用readonly关键字声明
编译期常量可以声明在可以声明在类或者方法中,运行时常量只能声明在类中不能声明在方法中

///
<summary> /// 编译时常量
/// </summary>
public const int ConstAge = 23;

///
<summary> /// 运行时常量
/// </summary>
public static readonly int ReadonlyAge = 23;

编译期常量与运行时常量的行为不同之处在于对他们的访问方式不同。编译期常量的值是在目标代码中进行替换的。如

if (ReadonlyAge == ConstAge)

将会与如下代码编译成同样的IL

if (ReadonlyAge == 23)

这种差异性会导致的后果就是当引用了编译时常量const关键字定义的常量时,若定义常量的程序集发生了变化,而引用的程序集没有重新编译的,这样就造成莫名其妙的错误。如:程序集 Utility中定义了一个const字段和一个readonly字段:

namespace Utility
{
    public class DemoClass
    {
        ///
<summary> /// 编译时常量
 /// </summary>
        public const int ConstNum = 3;
        public static readonly int ReadonlyNum = 3;
    }
}

在程序集ConsoleApplication1引用了Utility程序集,并进行如下调用:

static void Main(string[] args)
{
    Console.WriteLine("ConstNum:" + Utility.DemoClass.ConstNum);
    Console.WriteLine("ReadonlyNum:" + Utility.DemoClass.ReadonlyNum);
}

输出结果为:

ConstNum:3
ReadonlyNum:3

修改Utility程序集如下:

namespace Utility
{
    public class DemoClass
    {
        ///
<summary> /// 编译时常量
 /// </summary>
        public const int ConstNum = 10;
        public static readonly int ReadonlyNum = 10;
    }
}

更新ConsoleApplication1中对Utility程序集的引用,并没有重新编译ConsoleApplication1程序集,输出如下:

ConstNum:3
ReadonlyNum:10

没有如我们预料的那样输出为

ConstNum:10
ReadonlyNum:10

编译时常量只能定义为基本类型:整数、浮点数、枚举和字符串,其它类型即使为值类型也无法定义为常量const如:

///

 

/// 这样定义是不行滴,编译都通不过
///

 

public const DateTime Now = DateTime.Now;

总结:除非数据绝对不可能发生改变,否则不要定义public类型的const常量,若需要的话都定义成private类型的,实在要定义常量时请使用static readonly代替,一来readonly支持的类型更多也更灵活,二来const除了在性能上有那么丁点优势外其他的和static readonly没有任何区别

条目3 推荐使用 is 或 as 操作符而不是强制类型转换

需要使用类型转换的地方,尽量使用as操作符,强制类型转换可能会带来意向不到的负面影响,因为相对于强制类型转换来说,as更加安全,也更加高效,使用as进行转换时不会抛出异常,若转换失败后会返回NULL。

条目4 使用 Conditional 特性而不是#if 条件编译

这点感觉实际用处不大,毕竟实际中用#if的时候都不多,没必要花费那个精力去研究啥Conditional ,略过

条目5 为类型提供 ToString() 方法

同上,这点感觉实际用途不大,为每个实体类实现ToString()方法,有些画蛇添足的感觉,就为了观察每个类的属性信息,就来个 ToString()而且还要关心到底有没有把全部属性都加进去了,若修改了属性啥的咋办,而且代码越多出错概率也越大哈,除非闲的蛋疼否则不去干这费力 不讨好的事情

条目6 理解几个等同性判断之间的关系

实际用途不大,主要介绍了几种相等判断的方法,更多的篇幅用来介绍Equals方法并推荐去重写所有值类型的Equals方法,本人实在不敢苟同可能是咱的理解层次太低吧,而且我们又有多少实际定义值类型的机会呢

条目7 理解 GetHashCode() 的陷阱

关于这点咋说呢,俺基本上还木有使用过GetHashCode()做过啥实际有意义的事情(无意识中用到的不算),所以陷阱之类的对我来说不存在哈哈,略过,以后用到的时候再来好好研究

条目8 推荐使用查询语法而不是循环

这点主要是推荐大家在日常开发中使用linq语法代替繁杂的for、foreach、while循环(虽然有时感觉for循环也并不复杂,但显 然使用linq更简洁),带来的好处就是代码更加简洁,能够更加清晰的表达你的意图,可读性、易用性大幅高,而且也更加容易使用并行计算充分利用多核 CPU的优势,只需简单加上.AsParallel,并行编程就是这么简单

条目9 避免在API中使用转换操作符

事实上就是在任何地方都鲜有使用转换操符的应用,更别提在API中使用了,至少我还木有遇到过,略过

条目10 使用可选参数减少方法重载的数量

.Net4.0开始支持参数默认值了,应用参数默认值使参数变为可选参数,我们终于可以从无尽的重载中解脱出来了,当然可选参数也不是万能的,同条目1一样,修改了默认的参数值后,相关引用的程序集、代码都需要重新编译才可以,不过相对于这个小小的不便,我还是要为可选参数喝彩。
以前我们要实现如下的调用:
static void Main(string[] args)
{
SetUserName(“LazyRabbit”);
SetUserName(“LazyRabbit”, “cnblogs”);
}

需要写下面这样的两个重载才可以实现:

public static void SetUserName(string last)
{

}

public static void SetUserName(string last, string first)
{

}

来看看可选参数的魅力,我们只需要定义一个方法就ok了,实现一样的效果

public static void SetUserName(string last, string first = “LazyRabbit”)
{

}

条目11 理解短小方法的优势

我们平时编码很多人都知道尽量不要让一个方法代码过多,代码过多会造成可读性变差同时意味着单个方法的职责变多,不利于扩展、复用和修改,这里 介绍的观点不是讨论这些,主要介绍了JIT在编译阶段的优化,.Net运行时将调用JIT编译器来将C#编译器生成的IL翻译成机器码,这个过程平摊在应 用程序的整个生命周期中,JIT刚开始不会完全编译所有的IL,CLR会按照函数的粒度来逐一进行JIT编译,这样就大幅降低了程序启动的代码,没有被调 用的方法根本不会被JIT编译,因此将那些不是很重要的逻辑分支分解成更多的小分支要比把所有逻辑放到一起形成大型复杂函数有着更好的性能。短小的方法让 JIT能更好的平摊编译的代码,提高程序运行时的效率。如下代码,第一次调用DemoMethod方法时,if-else这两个分支都会被JIT编译,而实际上仅需要编译其中一个分支。

public static void DemoMethod(bool condition)
{

    if (condition)
    {
    //do Method1 thing
    }
    else
    {
    //do Method2 thing
    }
}

而若改成如下形式的话,在第一次调用DemoMethod方法时,仅会编译Method1()或者Method2()其中的一个,这样显然更加高效

public static void DemoMethod(bool condition)
{

    if (condition)
    {
    Method1();
    }
    else
    {
    Method2();
    }
}

public static void Method1()
{
    //do Method1 thing
}

public static void Method2()
{
    //do Method2 thing
}

以上代码有些牵强,而实际上到底能带来多大的提升呢,这个提升可能微乎其微,但可以肯定 的是方法越复杂带来的提升也就越大,同时方法越小也可以让JIT更容易进行寄存器选择工作,可以让局部变量存放在寄存器而不是栈上,更小的函数意味着更少 的局部变量,更方便JIT对寄存器进行优化。

至此,第一章 C#语言习惯阅读完毕,记录于此以作备忘和回顾,有些观点以前就已经知道了通过阅读加深了一些理解和认识,也有一些不知道的,感觉有用的就好好研读一番,感觉实用价值不高的也一行行看完,权当了解,没准哪天就用到了。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏