阅读笔记_《effective-csharp》
记录一下 《effective-csharp》 这本书里觉得比较有用的东东
gitbook:https://wizardforcel.gitbooks.io/effective-csharp/content/
索引器(indexers )
类似数组下标的方式 在类中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Abs {
public Dictionary<string, string> _map = new Dictionary<string, string>();
public string this[string key] {
get {
return _map.ContainsKey(key) ? _map[key] : null;
}
set {
_map.Add(key, value);
}
}
}
Abs a = new Abs();
a["aaa"] = "sdf";
属性访问器(getter setter)
在写c++的时访问属性一般都会提供个 getter setter 方法,方法体不大的时候编译器会做 inline 优化,在csharp中有这样的 语法糖,它提供了 inline 优化
1
2
3
4
5
6
7class AAA {
int mA = 0;
public int A {
get { return mA; }
set { mA = value; }
}
}如果不想被 inline 优化,可以使用 System.Runtime.CompilerServices.MethodImpl 特性, 指定方法不被内联
1
2[ ]
public int A(){}不会被 inline 优化的情况:虚函数 或者 包含 try/catch 的函数
偏爱 readonly 而不是 const
C# 有两种常量:编译时常量 const 和 运行时常量 readonly。
区别:
- 编译时常量 会比 编译时常量 稍微快点, 但更不灵活
- 编译时常量 还可以在方法体中声明。运行时常量 不能在方法体重声明
- 编译时常量 和 运行时常量 访问方式不同导致不同的行为。 在目标代码中 编译时常量 会被替换成 常量值;运行时常量 的值是在运行时得到的。 当你引用一个只读(read-only) 常量, IL 会引用一个readonly 变量而不是直接使用值
- 编译时常量只能在基本类型(内建整数和浮点数类型) , 枚举类型, 或字符串。 编译时常量要求类能用有意义的常量赋值初始化。 而只有基本类型才能在 IL 代码中使用常量(literal values) 来替换。 不能使用使用 new 操作法初始化编译时常量 ,即使它是一个值类型 。
- const编译时 编译到程序集,如果别的程序集使用了这个变量,这个变量将不会有任何改变;而readonly是运行时get到值,所以readonly跟灵活
示例:
1
2
3
4// Compile time constant:
public const int Millennium = 2000;
// Runtime constant:
public static readonly int ThisYear = 2004;
选择 is 或 as 而不是强制类型转换
使用as做类型转换,而不是强制装换(XXX)obj,如果obj没有继承关系,as编译时会报错,而(XXX)obj不会。
不能使用as的情况:参数为 值类型,此时可以用is来判断。(is只用于as无法使用的情况,否则多余,如果知道参数是引用类型就用 as)
示例
1
2
3
4
5object o = Factory.GetObject();
MyType t = o as MyType;
if (t != null) {
//Do somethind about t.
}
使用条件特性(conditional attribute) 代替 #if/#endif
假如调试代码是 CheckState 函数里的
1
2
3
4
5[ ]
private void CheckState()
{
// same code as above
}如果 DEBUG 变量被定义, 你的代码编译出来是这样的
1
2
3
4
5
6public string LastName {
get {
CheckState();
return lastName;
}
}如果 DEBUG 变量 没有 定义, 你的代码编译出来是这样的
1
2
3
4
5public string LastName {
get {
return lastName;
}
}如果使用 #if/#endif 块包住,也会产生一次 CheckState 函数的调用,即使 DEBUG 没有定义
1
2
3
4
5
6private void CheckState()
{
// same code as above
}Conditional 只能使用在 void 返回值的函数中,否则编译错误。
这种是 或 的表达式,如果要表达 与,拆分成两个方法,各用一个红
1
2
3
4
5
6
7
8
9[ ]
private void CheckState() {
}
//等价于
使用恰当的方式对静态成员进行初始化
- 静态初始化语法 会比 静态构造函数 更早执行,静态初始化语法 和 静态构造函数 是最干净, 最清晰的方式去初始化静态成员变量
实例第一次构造对象时的顺序
- static 变量默认存储为0。
- static 变量初始化执行。
- 基类静态构造函数执行。
- 静态构造函数执行。
- 实例成员变量默认存储为0。
- 实例成员变量初始化执行。
- 恰当的基类实例构造函数执行。
- 实例构造函数执行。
- 后续同一类型的对象初始化从第5步开始因为类初始化只会执行一次。 而且, 步骤6和7会被优化构造函数初始化语法会引起编译器移除重复的指令。
使用构造函数链
构造函数时 调用其他构造函数,减少各个构造函数中重复的初始化代码。类似重载函数的概念
1
2
3
4
5
6
7
8
9public class MyClass {// collection of data
private List<Circle> coll;
private string name;
public MyClass() : this(0, string.Empty) { }
public MyClass(int initialCount = 0, string name = "") {
coll = (initialCount > 0) ? new List<Circle>(initialCount) : new List<Circle>();
this.name = name;
}
}
using 语法糖
using 语法糖会产生 try/finally 块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16SqlConnection myConnection = null;
// Example Using clause:
using (myConnection = new SqlConnection(connString))
{
myConnection.Open();
}
// 等价于
// example Try / Catch block:
try
{
myConnection = new SqlConnection(connString);
myConnection.Open();
} finally {
myConnection.Dispose();
}快速保护方法 是使用 as 语句可以转换为安全可回收对象不管是否实现 IDisposable 接口
using 语句对在编译时期类型实现 IDisposable 接口才能正常工作
1
2
3
4
5// Does not compile. 编译报错
// Object does not support IDisposable.
using (object obj = Factory.CreateResource()) {
Console.WriteLine(obj.ToString());
}快速保护方法
1
2
3
4
5
6// The correct fix.
// Object may or may not support IDisposable.
object obj = Factory.CreateResource();
using (obj as IDisposable) { //如果 obj 没有实现 IDisposable 接口,则这行代码 等价于 using(null) {
Console.WriteLine(obj.ToString());
}
字符串拼接
- 使用 sting.Format 或者 StringBuilder 替代 string 的 += 拼接操作
- StringBuilder 类似 Vector 一样是动态扩容。
限制你的类型的可见性
- 尽可能的减少类的可见性,例如某些类里需要一些数据结构类,应该设为 private 或 这 internal ,不让外部知道
让接口支持协变和逆变
让接口支持 逆变、协变,限制 泛型只能是参数 或者 只能是返回值,例如Action 和 Func 这两个委托,这个有点抽象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public interface IMyList2<out T> {
T GetElement();
//void ChangeT(T t); // 这里编译错误,因为 T 被 out 修饰,所以 T 只能做返回值,不能做参数
}
public class MyList2<T> : IMyList2<T> {
public T GetElement() {
return default(T);
}
public void ChangeT(T t) {
//Change T
}
}
//-------------------------------
public interface IMyList<in T> {
//T GetElement(); // 这里编译错误,因为 T 被 in 修饰,所以 T 只能做参数,不能做返回值
void ChangeT(T t);
}
//没有指定 in 或者 out,就可以即做 参数 又做 返回值
public class MyList<T> : IMyList<T> {
public T GetElement() {
return default(T);
}
public void ChangeT(T t) {
//Change T
}
}
上面这些是个人感觉比较能用到的东西