本系列为阅读《Effective C#》一书所做的笔记,内容基本来自于该书籍与网上相关文档与博客;
由于主要起笔记作用,文章只记录了部分书籍中提到的优化要点,一些不常用的、本人暂时无法理解的、已经掌握或自以为已经掌握的内容不再做记录;
内容上比较简要,要获取具体的内容需要查阅相关的资料。
readonly & const
readonly 和 const 都是用于修饰常量的关键字,区别:
作用时期:
- const:作用于编译期常量,编译过程中编译器会直接将其值对变量进行替换,编译后就无法修改
- readonly:作用于运行期常量,运行时执行完构造函数后无法进行修改
作用范围:
- const:作用于内建类型,int,float,字符串等
- readonly:可作用于自定义类型
访问方式:
- const:编译期直接做了字面值替换,故访问时也直接访问值
- readonly:通过引用访问(对于内建类型也是如此)
as & is 与强制类型转换
示例
// as 转换
object o = Factory.GetObject();
MyType t = o as MyType;
if (t != null) {
// 转换成功
} else {
// 转换失敗
}
// 强制转换
object o = Factory.GetObject();
try {
MyType t = (MyType) o;
if (t != null) {
// 转换成功
} else {
// 转换失敗
}
} catch {
// 异常处理
}
差异
- as & is 相对强制转换更安全,运行时效率相对高
- as & is 转换不用做异常处理
- as & is 不执行任何用户自定义的转换
- as 不能用于值类型,因为值类型不能为 null(C# 2.0 后的 Nullable 类型可以支持)
Nullable 类型
Nullable types (C# Programming Guide)
int number = 1024;
object o = (object)number;
int? t = o as int;
if (t != null) {
// 转换成功
} else {
// 转换失敗
}
或者可以使用 is:
object tempFoo = container.Resolve<Foo>(); // 获取为 Foo 类型
int i = 0; // 值类型转换
if (tempFoo is int) {
i = (int)tempFoo;
}
object tempFoo = container.Resolve<Foo>(); // 获取为 Foo 类型
Logger myFoo = null; // 引用类型转换
if (tempFoo is Logger) {
myFoo = tempFoo as Logger;
}
用户自定义转换
例如:
public class MyClass {
private Logger _value;
// 隐式自定义类型转换
public static implicit operator Logger(MyClass cls) {
return cls._value;
}
}
类型转换分编译时及运行时,用户自定义类型在做隐式类型转换不支持在运行时转换,故下面会抛出异常:
object obj = factory.GetObject<MyClass>();
try {
// 编译时没有定义 object 到 Logger 的转换
Logger l = (Logger)obj;
if (l != null) {
Console.WriteLine("convert succ");
}
} catch {
Console.WriteLine("convert fail");
}
// output: convert fail
需要将类型转换移到编译时:
object obj = factory.GetObject<MyClass>();
MyClass cls = obj as MyClass;
try {
Logger l = (Logger)cls;
if (l != null) {
Console.WriteLine("convert succ");
}
} catch {
Console.WriteLine("convert fail");
}
// output: convert succ
delegate
博客参考:
示例:
class MainClass {
public delegate void DelegateTest(string str);
public static void Main(string[] args) {
DelegateTest test;
test = (str) => { Console.WriteLine("Hello " + str); };
test("World");
}
}
注意:
delegate 都是多播委托(multicast delegate),即所有目标函数会视为一个整体执行:
- 若其中一个函数执行发生错误,后续的函数将不会执行
- 若 delegate 定义了返回值,则其返回值为最后一个执行函数的返回值
如果需要处理异常或者返回值,则需要手动执行 delegate,例如:
class MainClass {
public delegate bool DelegateTest(string str);
public static void Main(string[] args) {
DelegateTest test;
test = (str) => {
Console.WriteLine("Hello " + str);
return true;
};
test += (str) => {
Console.WriteLine("Good Morning " + str);
return true;
};
test += (str) => {
Console.WriteLine("Bye " + str);
return true;
};
bool ret = true;
foreach (DelegateTest func in test.GetInvocationList()) {
ret &= func("World");
if (!ret)
break;
}
}
}
delegate & event:
开发中经常使用到事件系统,用 delegate 可以比较简单地完成需求,但存在的问题有:
- delegate 不能为 private,否则外部无法访问
- delegate 若为 public,则容易被外部修改
故提供了 event 关键字,用于修饰 delegate 的声明,其修饰的 delegate 变量会默认为 private (不管声明为 public 还是其它),但外部能够通过 += 和 -= 两种操作符来实现事件的监听与去监听:
class SomeClass {
public delegate bool DelegateTest(string str);
public event DelegateTest testEvt;
public void SomeFunc() {
testEvt("World");
}
}
class MainClass {
public static void Main(string[] args) {
SomeClass cls = new SomeClass();
cls.testEvt += (str) => {
Console.WriteLine("Hello " + str);
return true;
};
cls.testEvt += (str) => {
Console.WriteLine("Good Morning " + str);
return true;
};
cls.testEvt += (str) => {
Console.WriteLine("Bye " + str);
return true;
};
cls.SomeFunc();
}
}
box & unbox
定义:
- 装箱:值类型放入非类型化的引用对象中(object)
- 拆箱:从已经装箱的对象中,将值拷贝一份出来
开销原因:
- 装箱、拆箱本身的消耗
- 装箱时除了拷贝原值,还会实现其实现的接口(例如对于struct)
- 装箱时分配了heap内存,产生了gc
- 拆箱时每次都需要从箱中拷贝一次原值
注意点:
隐式转换:
// origin
int number = 1024;
string str = string.Format("number={0}", number);
// 等同于
int number = 1024;
object number_box = (object)number;
int number_unbox = (int)number_box;
string number_str = number_unbox.ToString();
string str = string.Format("number={0}", number_str);
new 修饰符
作用
修饰子类中与父类同名的非虚方法,用于做出不同的表现
public class Base {
public void Func() {
Debug.Log("Base 111");
}
}
public class Derived {
public void Func() {
Debug.Log("Derived 222");
}
}
// ...
object obj = FactoryFunc();
Base b = obj as Base;
b.Func(); // 'Base 111'
Derived d = obj as Derived;
d.Func(); // 'Derived 222'
缺陷
问题在于,使用 new 修饰符会导致,同一个对象的同一个接口会具有不同的表现(取决于作为父类还是子类的引用),容易造成误解