Learning CLR via C#(2)
Tips:
- 可选参数和默认参数的配合:
static void Test() { M(c: 5); } static void M(int a = 1, int b = 2, int c = 3) { //do sth }这样可以前两个参数使用默认值。
- 从CLR的角度看,out和ref完全一致。C#编译器将这两个关键字区别对待,这个区别决定了由哪个方法负责初始化所引用的对象。
- 可以为索引器重命名,默认为Item。可通过属性System.Runtime.CompilerServices.IndexerNameAttribute重命名。
- 泛型方法,对于任何引用类型,都会使用相同的代码。因为本质上都是操作一个指针。
- 逆变量参数只能出现在输入位置,比如作为方法的参数。协变量参数只能出现在输出位置,比如作为方法的返回类型。
IEnumerable<object> objs = new List<string>();这个表达式看起来非常自然,可惜的是Framework4.0之前这样赋值是不可能的。
因为IEnumerable<out T>的泛型参数是一个协变量。仔细考虑下,objs的返回值需要一个object类型,但是我们传入的是一个能返回string类型的List,那么无疑,这个传入的值是有效的,因为string类型是object的子类。
相反,看如下的表达式:
Action<string> b = new Action<object>(o => Console.WriteLine(o.GetType()));这个就比较违反我们的思维定势了,怎么能把一个object类型隐式转型赋值给一个string类型?
因为Action<in T>的泛型参数是一个逆变量。仔细想想,如果现在有一个委托只需要object类型的参数就可以工作,我们给它一个string当然没问题。
总结一下就是,协变量可以由他的派生类更改为他的基类,逆变量可以由他的基类更改为派生类。
public delegate TResult Func<in T, out TResult>(T arg);看这个委托的定义,我们可以这样使用:
Func<string, Exception> fn2 = new Func<object, ArgumentException>((o) => null);主要是看转换过程。
第一个泛型参数是逆变量,因为他从object更改成了string,即从基类更改为派生类。第二个是协变量,因为它从ArgumentException更改成Exception,即从派生类更改为基类。
- 类型参数可以指定零个或一个主要约束(引用类型、struct、class),零个或多个次要约束(接口、类型参数约束),零个或一个构造器约束(无参)。
- 将一个泛型参数的变量转型为另一个参数是非法的,除非将其转型为与一个约束兼容的类型。
private void Cast<T>(T generic) { int a = (int)generic; string b = (string)generic; }上述代码会编译失败,若要编译成功,可以先转型为objectprivate void Cast<T>(T generic) { int a = (int)(object)generic; string b = (string)(object)generic; }
- default(T)告诉C#编译器和CLR的JIT编译器,如果T是一个引用类型,就将其设为null,如果是值类型,就将temp的所有位设为0.
- 无论泛型是否被约束,使用==或!=操作符将一个泛型类型变量与null进行比较都是合法的:
private void Cast<T>(T generic) { if (generic == null) { //do sth } }