1.8公共语⾔规范
  COM允许使⽤不同语⾔创建的对象相互通信你。现在,CLR集成了所有语⾔,允许在⼀种语⾔中使⽤由另⼀种语⾔创建的对象。之所以能实现这样的集成,是因为CLR使⽤了标准类型集、元数据(⾃描述的类型信息)以及公共执⾏环境。
  语⾔的集成是⼀个宏伟的⽬标,最棘⼿的问题是各种编程语⾔存在极⼤的区别。例如,有的语⾔在处理符号时不区分⼤⼩写,有的语⾔不⽀持
unsigned(⽆符号)整数、操作符重载或者参数数量可变的⽅法。
  要创建很容易从其他编程语⾔中访问的类型,只能从⾃⼰的编程语⾔中挑选其他所有语⾔都确定⽀持的那些功能。为了在这个⽅⾯提供帮助,Microsoft定义了⼀个“公共语⾔规范”(Common Language Specification,CLS),它详细定义了⼀个最⼩功能集。任何编译器⽣成的类型要想兼容于由其他“符合CLS、⾯向CLR的语⾔”所⽣产的组件,就必须⽀持这个最⼩功能集。
  CLR/CTS⽀持的功能⽐CLS定义的⼦集多得多。如果不关⼼语⾔之间的互操作性,可以开发⼀套功能⾮常丰富的类型,它们仅受你选⽤的那种语⾔的功能集的限制。具体地说,在开发类型和⽅法的时候,如果希望它们对外“可见”,能够从符合CLS的任何⼀种编程语⾔中访问,就必须遵守由CLS定义的规则。注意,
假如代码只是从定义(这些代码的)程序集的内部访问,CLS规则就不适⽤了。图1-6形象地演⽰了这⼀段想要表达的意思。
图1-6  每种语⾔都提供了CLR/CTS的⼀个⼦集以及CLS的⼀个超集(但不⼀定是同⼀个超集) 
  如图1-6所⽰,CLR/CTS提供了⼀个功能集。有的语⾔公开了CLR/CTS的⼀个较⼤的⼦集。例如,假定开发⼈员使⽤IL汇编语⾔写程序,就可以使⽤
CLR/CTS提供的全部功能。但是,其他⼤多数语⾔(⽐如C#、Visual Basic和Fortran)只向开发⼈员公开了CLR/CTS的⼀个功能⼦集。CLS定义了所有语⾔都必须⽀持的⼀个最⼩功能集。
  ⽤⼀种语⾔定义⼀个类型时,如果希望在另⼀种语⾔中使⽤该类型,就不要在该类型的public和protected成员中使⽤位于CLS外部的任何功能。否则,其他开发⼈员使⽤其他语⾔写代码时,就可能⽆法访问这个类型的成员。
  以下代码使⽤C#定义⼀个符合CLS的类型。然⽽,类型中含有⼏个不符合CLS的构造,造成C#编译器报错: 
using System;
//  告诉编译器检查CLS相容性
[assembly: CLSCompliant(true)]
namespace SomeLibrary
{
//  因为是public类,所以会显⽰警告
public sealed class SomeLibraryType
{
//  警告:SomeLibrary.SomeLibraryType.Abc()的返回类型不符合CLS
public UInt32 Abc() { return 0; }
//  警告:仅⼤⼩写不同的标识符SomeLibrary.SomeLibraryType.abc()不符合CLS
public void abc() { }
//  不会显⽰警告:该⽅法是私有的
private UInt32 ABC() { return 0; }
}
}
  上述代码将[assembly:CLSCompliant(true)]这个attribute1应⽤于程序集。这个attribute告诉编译器检查public类型,判断是否存在任何不合适的构造,阻⽌了从其他编程语⾔中访问该类型。上述代码编译时,C#编译器会报告两条警告消息。第⼀个警告是因为Abc⽅法返回了⼀个⽆符号整数;有⼀些语⾔是不能操作⽆符号整数值的。第⼆个警告是因为该类型公开了两个public⽅法,这两个⽅法(Abc和abc)只是⼤⼩写和返回类型有别。Visual Basic和其他⼀些语⾔⽆法区
别这两个⽅法。
  现在,让我们提炼⼀下CLS的规则。在CLR中,⼀个类型的每个成员要么是⼀个字段(数据),要么是⼀个⽅法(⾏为)。这意味着每⼀种编程语⾔都必须能访问字段和调⽤⽅法。这些字段和⽅法通过特殊或者通⽤的⽅式来使⽤。为了编程进⾏编程,语⾔通常提供了额外的抽象,对这些常见的编程模式进⾏简化。例如,语⾔可能公开枚举、数组、属性、索引器、委托、事件、构造器、析构器、操作符重载、转换操作符等概念。编译器在源代码中遇到上述任何⼀种构造,必须将其转换成字段和⽅法,使CLR和其他编程语⾔能够访问这些构造。
  在以下类型定义中,包含⼀个构造器、⼀个析构器、⼀些重载的操作符、⼀个属性、⼀个索引器以及⼀个事件。注意,这些代码的⽬的只是让代码更够编译,并不代码实现⼀个类型的正确⽅式。 
using System;
internal sealed class Test
{
//  构造器
析构方法
public Test() { }
//  析构器
~Test() { }
//  操作符重载
public static bool operator ==(Test t1, Test t2)
{
return true;
}
public static bool operator !=(Test t1, Test t2)
{
return false;
}
//  ⼀个操作符重载
public static Test operator +(Test test1, Test test2) { return null; }
//  ⼀个属性
public string AProperty
{
get { return null; }
set { }
}
//  ⼀个索引器
public string this[int x]
{
get { return null; }
set { }
}
//  ⼀个事件
event EventHandler AnEvent;
}
  编译器编译上述代码会得到⼀个类型,其中包含⼤量字段和⽅法。可以使⽤.NET Framework SDK配套提供的IL反汇编器⼯具()来检查最终⽣成的托管模块,如果1-7所⽰。
图1-7  ILDasm显⽰了Test类型的字段和⽅法(从元数据中提取)
  表1-4总结了编程语⾔的各种构造与CLR字段CLR字段/⽅法的对应关系。
  表1-4  Test类型的字段和⽅法(从元数据中提取)
类型的成员成员的类型对应的编程语⾔构造
AnEvent字段事件;字段名是AnEvent,类型是
System.EventHandler
.ctor⽅法构造器
Finalize⽅法析构器
add_AnEvent⽅法事件的add访问器⽅法
get_AProperty⽅法属性的get访问器⽅法
get_Item⽅法索引器的get访问器⽅法
op_Addition⽅法+操作符
op_Equality⽅法==操作符
op_Inequality⽅法!=操作符
remove_AnEvent⽅法事件的remove访问器⽅法
set_AProperty⽅法属性的set访问器⽅法
set_Item⽅法索引器的set访问器⽅法
  Test类型还有另⼀些节点未在表1-4中列出,其中包括.class,.custom,AnEvent,AProperty以及Item--它们标识了与类型有关的其他元数据。这些节点不映射到字段或⽅法;它们只是提供了有关类型的⼀些额外的信息,供CLR、编程语⾔或者⼯具访问。例如,利⽤某个⼯具,可以发现Test类型提供了⼀个名为AnEvent的事件,该事件通过两个⽅法(add_AnEvent和remove_AnEvent)公开。
  1、在.NET Framework SDK⽂档中,attribute和property均被翻译成属性。⼀些⼈将attribute翻译成特性,将property翻译成属性。但为了避免造成困扰,本书保留attribute原⽂,但将property翻译成属性。