《CLR via C#》精髓:CLR永远都不会知道的那些事
1、CLR根本不知道“命名空间”(Namespace)这回事
C#编译器编译你的代码时,会自动把命名空间名称和类型名称拼到一起,形成类型全名(Full name,如:“System.IO”+“FileStream”形成“System.IO.FileStream”)。CLR对此一无所知,他看到的永远都是类型全名,并认为类型名称理应如此。
C#引入命名空间只是为了让你少打几个字,不必每次都繁琐地写下一个类型的完整名称。另外,命名空间还是一种对类型进行逻辑分类的方法(让相似功能或用途的类型在同一个命名空间内)。
但是,请记住,命名空间名称与程序集(Assembly)名称没有半点关系,虽然大多数程序集(尤其是FCL中)的名称里都包含了命名空间,但这只是一种习惯,并不是一种标准,一个程序集内可能包含多个命名空间,一个命名空间也可能存在于多个程序集内。
2、CLR不知道什么叫“可选参数”(Optional Parameter)
当C#编译器发现你为一个方法的参数设定了一个默认值时,他会“偷偷地”在参数上做上些标记(自动增加两个特性:System.Runtime.InteropServices.OptionalAttribute和System.Runtime.InteropServices.DefaultParameterValueAttribute),并在编译时为其生成特殊元数据(你所设定的默认值也将以常量的形式存于元数据中)。当该方法被调用时,如果传递的参数省略了这个可选参数,C#编译器会从元数据中取出默认值,自动为你补全参数。对于上述整个过程,CLR全然不知。
3、CLR“不知有var,无论dynamic”
详见既生dynamic何生var一文。
4、CLR认为ref和out没有区别
C#编译器会为ref和out生成完全相同的元数据,所以在CLR看来他们没有区别。相反,C#编译器对开发者则比较“苛刻”。他让我们区分ref和out,其实是希望我们能够显式地表明我们的意图:参数会在方法外被初始化(ref)还是在方法内被初始化(out)。当C#编译器通过ref或out窥探到我们的意图后,便可据此完成语法检测,以保证参数确实在我们所设想的地方完成初始化。
顺便说一下,CLR允许基于方法参数是否使用了ref和out来进行方法重载(Overload),例如:
public sealed class Point { static void Add(Point p) { } static void Add(ref Point p) { } }
但不允许重载方法时仅通过ref和out区分,例如:
// 编译出错 public sealed class Point { static void Add(ref Point p) { } static void Add(out Point p) { } }
原因很简单:CLR认为ref和out是一回事。
5、CLR不认识params关键字
C#向我们承诺,只要声明方法参数时使用了params关键字,以后就可以向该方法传递无限数量的参数。String类型的Format静态方法就是这方面的典型应用。但是,CLR根本就不认得params这个词,更不可能允许一个方法接受无限多个参数。
事实上,当C#编译器发现我们声明方法参数时使用了params关键字后,他将自动为此参数添加一个System.ParamArrayAttribute特性。当此方法被调用时,C#编译器会将以逗号分隔的参数序列打包为一个数组后传给该方法。CLR对此过程一无所知,只知道一个带有数组参数的方法被调用了。
6、(待续)

浙公网安备 33010602011771号