前言
以下的总结是笔者在阅读《Effective C#》时做的笔记,本书的编程环境时C#6.0,但笔者主要会从Unity开发着的角度剔除其中在Unity编程中无法用到的建议来方便Unity开发者阅读。
C#语言的编程习惯
能用的东西为什么要改?因为改了之后效果更好。C++,Java的开发者会习惯的把他们之前的开发习惯带到C#中来,但这有时候不是一件好事,这一部分会让大家知道C#的语言编程习惯。
优先使用隐式类型的局部变量(使用var)
用var来声明变量可以使开发者把精力集中在名称上面,从而更好的了解其含义。例如:
var joybsQueuedByRegion = new Dicitionary<int, Queue<string>();
这个变量读者从名字上即可知道其含义,同时在看到右侧时,读者即可知道其具体类型,此时并不需要需要声明其具体类型。
但这个建议说的是优先使用,意思是除非开发者必须看到变量的声明类型后才能正确理解代码的含义,否则就可以考虑用var来声明局部变量。
但并不是所有的情况都能使用var,盲目的使用可能会导致bug。例如对int,float,double等**数值类型,**就应该使用其具体类型。
var f = GetMagicNumber();
var total = 100 * f / 6;
上面total的值为多少呢,具体得由GetMagicNumber的返回类型来确定,而使用var我们将无法知道其类型,从而可能出现类型转换而导致精度相关的问题。
考虑用readonly代替const
C#的两种常量
- 编译期常量
- 特点:能使程序运行稍快一些,但不入运行其常量灵活,可以在方法里声明
- 用const关键字来声明
- 运行期常量
- 特点:使用起来比较灵活,但运行效率比编译期常量稍低,不可以在方法里声明
- 用readonly关键字来声明
// 编译期常量
public const int Millennium = 2000;
//运行期常量
public static readonly int ThisYear = 2019;
两种常量访问时的区别
- 编译期常量
编译期的常量其取值会嵌入代码,例如:
if(myDateTime.Year = Millennium)
以上代码在编译成IL之后与直接使用字面量2000的写法时一样的:
if(myDateTime.Year == 2000)
- 运行期常量
如果代码中使用到了这种常量,那么由该代码生成的IL会通过引用的方式来使用这个常量。
两种常量所支持的值的区别
- 编译期常量
只能用来表示内置的整数,浮点数,枚举,字符串,只有用来表示这些原始类型的编译常量才会替换成字面量,因此下面这条语句时无法编译的,因为他试图用new来对编译期常量做初始化:
//无法编译
private const DateTime classCreation = new DateTime(2000,1,1,0,0,0);
编译期常量只能用数字,字符串或null来初始化。
- 运行期常量
类型不受限制,例如上面的DateTime不能使用const来声明,但可以使用readonly来声明。
另外,readonly可以用来声明实例级别的常量,而const声明的编译期常量则是静态常量。
程序集的修改
若程序集中使用了编译期常量,对其修改时,凡是使用改常量的代码都必须重新编译,可维护性比较低,而修改访问级别为public的readonly常量则相当于修改实现细节,并不影响现在的引用了改常量的程序集。
优先使用is或as运算符,尽量少用强制类型转换
使用内插字符串取代string.Format
注意内插字符串是C#6.0的特性,Unity5.x不支持,Unity2017起才能支持。
String.Format的缺点
String.Format所有的替换操作都是根据格式化字符串里面的序号来完成的,但比编译器不会去验证格式化字符串后面参数个数与有待替换的序号数量是否相等,如果不等,那么程序在运行的时候就会抛出异常。另外一个问题则是,阅读代码的人不太容易看出来params数组中的位置是否与格式化字符串中的序号对应,这一点需要到运行时才能确认(当然,IDE支持的话时可以看到对应的位置的)
内插字符串的优点
内插字符串以$开头,可以直接在花括号里编写C#表达式,这使的代码便于阅读,因为开发者可以看到这些有待替换的内容分别对应于什么样的表达式。
内插字符串的限制
- 花括号里的是表达式,所以不能使用if/else或while等控制流语句来做替换,如果需要根据控制流做替换,那么必须将这些逻辑写成方法,然后在内插字符串里面嵌入该方法的调用结果。