语言独立性和与语言无关的组件Language Independence and Language-Independent Components

本文内容

.NET Framework 是独立于语言的。这意味着,作为开发人员,您可以使用面向 .NET Framework 的多种语言(例如,C#、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual Basic、Visual COBOL 以及 Windows PowerShell)之一进行开发。您可以访问针对 .NET Framework 开发的类库的类型和成员,而不必了解它们的初始编写语言,也不必遵循任何原始语言的约定。如果您是组件开发人员,无论组件采用哪种语言,均可由任何 .NET Framework 应用程序访问。

备注

本文的第一部分讨论如何创建独立于语言的组件(即以任何语言编写的应用均可以使用的组件)。还可以从用多种语言编写的源代码创建单个组件或应用;请参阅本文第二部分的跨语言互操作性

若要与使用任何语言编写的其他对象完全交互,对象必须只向调用方公开那些所有语言共有的功能。此组通用功能由公共语言规范 (CLS) 定义,后者是一组适用于生成的程序集的规则。公共语言规范在 ECMA-335 标准:公共语言基础结构的第 I 部分的第 7 条至第 11 条中进行了定义。

如果你的组件符合公共语言规范,则保证其符合 CLS 并可通过支持 CLS 的任何编程语言编写的程序集中的代码对其进行访问。你可以通过将 CLSCompliantAttribute 特性应用于源代码来确定自己的组件在编译时是否符合公共语言规范。有关详细信息,请参阅 CLSCompliantAttribute 特性

本文内容:

CLS 遵从性规则CLS compliance rules

本节讨论用于创建符合 CLS 的组件的规则。有关规则的完整列表,请参阅 ECMA-335 标准:公共语言基础结构的第 I 部分的第 7 条至第 11 条中进行了定义。

备注

公共语言规范讨论 CLS 遵从性的每个规则,因为它应用于使用者(以编程方式访问符合 CLS 的组件的开发人员)、框架(使用语言编译器创建符合 CLS 的库的开发人员)和扩展人员(创建可创建符合 CLS 的组件的语言编译器或代码分析器等工具的开发人员)。本文重点介绍适用于框架的规则。但请注意,一些适用于扩展程序的规则也适用于使用 Reflection.Emit 创建的程序集。

若要设计独立于语言的组件,您只需将 CLS 遵从性的规则应用于组件的公共接口即可。您的私有实现不必符合规范。

重要

CLS 遵从性的规则仅适用于组件的公共接口,而非其私有实现。

例如,Byte 之外的无符号整数不符合 CLS。由于以下示例中的 Person 类公开了 Age 类型的 UInt16 属性,因此以下代码显示编译器警告。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Person
  4. {
  5. private UInt16 personAge = 0;
  6. public UInt16 Age
  7. { get { return personAge; } }
  8. }
  9. // The attempt to compile the example displays the following compiler warning:
  10. // Public1.cs(10,18): warning CS3003: Type of 'Person.Age' is not CLS-compliant
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Person
  3. Private personAge As UInt16
  4. Public ReadOnly Property Age As UInt16
  5. Get
  6. Return personAge
  7. End Get
  8. End Property
  9. End Class
  10. ' The attempt to compile the example displays the following compiler warning:
  11. ' Public1.vb(9) : warning BC40027: Return type of function 'Age' is not CLS-compliant.
  12. '
  13. ' Public ReadOnly Property Age As UInt16
  14. ' ~~~

您可以通过将 Person 属性的类型从 Age 更改为 UInt16 来将 Int16 类设置为符合 CLS,即一个符合 CLS 的 16 位带符号整数。您无需更改私有 personAge 字段的类型。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Person
  4. {
  5. private Int16 personAge = 0;
  6. public Int16 Age
  7. { get { return personAge; } }
  8. }
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Person
  3. Private personAge As UInt16
  4. Public ReadOnly Property Age As Int16
  5. Get
  6. Return CType(personAge, Int16)
  7. End Get
  8. End Property
  9. End Class

库的公共接口包括:

  • 公共类的定义。

  • 公共类中公共成员的定义,以及派生类可以访问的成员(即受保护的成员)的定义。

  • 公共类中公共方法的参数和返回类型,以及派生类可以访问的方法的参数和返回类型。

下表中将列出 CLS 遵从性规则。规则的文本摘自 ECMA-335 标准:公共语言基础结构(版权所有 2012,Ecma International)。有关这些规则的详细信息,请参阅以下各节。

类别查看规则规则编号
可访问性成员可访问性重写继承的方法时,可访问性应不会更改(重写一个继承自其他具有可访问性 family-or-assembly 的程序集的方法除外)。在此情况下,重写应具有可访问性 family10
可访问性成员可访问性类型和成员的可见性和可访问性应是这样的:只要任何成员是可见的且可访问的,则该成员签名中的类型应是可见且可访问的。例如,在其程序集外部可见的公共方法不应具有其类型仅在程序集内可见的参数。只要任何成员是可见且可访问的,则构成该成员签名中使用的实例化泛型类型的类型应是可见且可访问的。例如,一个在其程序集外部可见的成员签名中出现的实例化泛型类型不应具有其类型仅在程序集内可见的泛型实参。12
数组数组数组应具有符合 CLS 的类型的元素,且数组的所有维度的下限应为零。各重载间只需区别:项是数组还是数组的元素类型。当重载基于两个或更多数组类型时,元素类型应为命名类型。16
特性特性特性应是 System.Attribute 类型或从该类型继承的类型。41
特性特性CLS 只允许自定义特性编码的子集。这些编码中仅应显示的类型(请参阅第 IV 部分):System.TypeSystem.StringSystem.CharSystem.BooleanSystem.ByteSystem.Int16System.Int32System.Int64System.SingleSystem.Double 以及基于符合 CLS 的基整数类型的任何枚举类型。34
特性特性CLS 不允许公开可见的所需修饰符(modreq,请参阅第 II 部分),但允许其不了解的可选修饰符(modopt,请参阅第 II 部分)。35
构造函数构造函数在访问继承的实例数据之前,对象构造函数应调用其基类的某个实例构造函数。(这不适用于不需要构造函数的值类型。)21
构造函数构造函数对象构造函数只能在创建对象的过程中调用,并且不应将对象初始化两次。22
枚举枚举枚举的基础类型应是内置的 CLS 整数类型,字段的名称应为“value__”,并且应将该字段标记为 RTSpecialName7
枚举枚举有两种不同的枚举,二者由是否存在 System.FlagsAttribute(请参阅第 IV 部分库)自定义特性指示。一个表示命名的整数值;另一个表示命名的位标记(可合并以生成一个未命名的值)。enum 的值不仅限于指定的值。8
枚举枚举枚举的文本静态字段需具有枚举本身的类型。9
事件事件实现事件的方法将在元数据中标记为 SpecialName29
事件事件事件及其访问器的可访问性应相同。30
事件事件事件的 addremove 方法应同时存在或不存在。31
事件事件事件的 addremove 方法均应采用一个参数,该参数的类型定义了事件的类型,且应派生自 System.Delegate32
事件事件事件应遵循特定的命名模式。CLS 规则 29 中引用的 SpecialName 特性应在适当的名称比较中被忽略,且应遵循标识符规则。33
异常异常引发的对象应为 System.Exception 类型或从该类型继承的类型。但是,阻止其他类型的异常的传播无需符合 CLS 的方法。40
常规CLS 符合性:规则CLS 规则仅适用于类型中在定义程序集之外可访问或可显示的部分。1
常规CLS 符合性:规则不应将不符合 CLS 的类型的成员标记为符合 CLS。2
泛型泛型类型和成员嵌套类型拥有的泛型形参的数目至少应与封闭类型的一样多。嵌套类型中的泛型参数在位置上与其封闭类型中的泛型参数对应。42
泛型泛型类型和成员根据上面定义的规则,泛型类型的名称将对非嵌套类型上声明的或新引入到类型(如果嵌套)中的类型参数的数量进行编码。43
泛型泛型类型和成员泛型类型应该重新声明足够多的约束,以确保对基类型或接口的任何约束能够满足泛型类型约束的需要。4444
泛型泛型类型和成员用作对泛型参数的约束的类型本身应符合 CLS。45
泛型泛型类型和成员实例化泛型类型中的成员(包括嵌套类型)的可见性和可访问性应被认为限制在特定的实例化中,而不是限制在整个泛型类型声明中。作出以下假定:CLS 规则 12 的可见性和可访问性仍适用。46
泛型泛型类型和成员对于每个抽象或虚拟泛型方法,应该有默认的具体(非抽象)实现。47
接口接口符合 CLS 的接口不需要不符合 CLS 的方法的定义即可实现这些方法。18
接口接口符合 CLS 的接口不应定义静态方法,也不应定义字段。19
成员类型成员概述全局静态字段和方法不符合 CLS。36
成员通过使用字段初始化元数据指定文本静态值。符合 CLS 的文本必须在类型与文本(或基本类型,如果文本为 enum)完全相同的字段初始化元数据中指定值。13
成员类型成员概述vararg 约束不属于 CLS,并且 CLS 支持的唯一调用约定是标准托管调用约定。15
命名约定命名约定针对用于管理允许启用且包含在标识符中的字符集的 Unicode Standard3.0 ,程序集应遵守其技术报告 15 的附件 7(可通过访问 https://www.unicode.org/unicode/reports/tr15/tr15-18.html 在线获得)。标识符应是由 Unicode 范式 C 定义的规范格式。对于 CLS,如果两个标识符的小写映射(由不区分区域设置的 Unicode、一对一小写映射指定)相同,则它们也相同。也就是说,对于要在 CLS 下视为不同的两个标识符,它们应以大小写之外的差别进行区分。但是,若要重写继承的定义,CLI 需要对使用的原始声明进行准确编码。4
重载命名约定在符合 CLS 的范围中引入的所有名称都应是明显独立的类型,除非名称完全相同且通过重载解析。也就是说,CTS 允许单个类型对方法和字段使用相同的名称,但 CLS 不允许。5
重载命名约定即使 CTS 允许区分不同的签名,但字段和嵌套类型只能由标识符比较区分。(标识符比较后)具有相同名称的方法、属性和事件应在除返回类型不同之外还具有其他差异,CLS 规则 39 中指定的差异除外。6
重载重载只可重载属性和方法。37
重载重载属性和方法仅可基于其参数的数目和类型进行重载,名为 op_Implicitop_Explicit 的转换运算符除外,这两种转换运算符也可基于其返回类型进行重载。38
重载如果类型中声明的两种或更多符合 CLS 的方法都具有相同的名称,并且对于特定的类型实例化集而言,它们具有相同的参数和返回类型,则所有这些方法在这些类型实例化时都应在语义上保持等效。48
类型类型和类型成员签名System.Object 符合 CLS。任何其他符合 CLS 的类应从符合 CLS 的类继承。23
属性属性实现属性的 getter 和 setter 方法的方法应在元数据中标记为 SpecialName24
属性属性属性的访问器必须同为静态、同为虚拟或同为实例。26
属性属性属性的类型应该是 getter 的返回类型和 setter 的最后一个自变量的类型。属性参数的类型应是对应于 getter 的参数的类型和 setter 的除最后一个参数之外所有参数的类型。所有这些类型都应该符合 CLS,并且不应是托管指针(也就是说,不应该被引用传递)。27
属性属性属性应遵循特定的命名模式。CLS 规则 24 中引用的 SpecialName 特性将在适当名称比较中被忽略,且应遵循标识符规则。属性应具有 getter 和/或 setter 方法。28
类型转换类型转换如果提供 op_Implicitop_Explicit,则必须提供实现强制转换的替代方法。39
类型类型和类型成员签名装箱的值类型不符合 CLS。3
类型类型和类型成员签名显示在签名中的所有类型应符合 CLS。构成实例化泛型类型的所有类型应符合 CLS。11
类型类型和类型成员签名类型化的引用是不符合 CLS 的。14
类型类型和类型成员签名非托管的指针类型不符合 CLS。17
类型类型和类型成员签名符合 CLS 的类、值类型和接口不应要求实现不符合 CLS 的成员。20

类型和类型成员签名Types and type member signatures

System.Object 类型符合 CLS,并且是 .NET Framework 类型系统中所有对象类型的基础类型。.NET Framework 中的继承要么是隐式的(例如,String 类从 Object 类隐式继承),要么是显式的(例如,CultureNotFoundException 类从 ArgumentException 类显式继承,其中后者从 SystemException 类显式继承,而该类又从 Exception 类显式继承)。对于要符合 CLS 的派生类型,其基本类型也必须符合 CLS。

下面的示例显示基本类型不符合 CLS 的派生类型。它定义使用无符号 32 位整数作为计数器的基本 Counter 类。由于类通过对无符号整数进行换行来提供计数器功能,因此类标记为不符合 CLS。因此,派生类 NonZeroCounter 也不符合 CLS。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. [CLSCompliant(false)]
  4. public class Counter
  5. {
  6. UInt32 ctr;
  7. public Counter()
  8. {
  9. ctr = 0;
  10. }
  11. protected Counter(UInt32 ctr)
  12. {
  13. this.ctr = ctr;
  14. }
  15. public override string ToString()
  16. {
  17. return String.Format("{0}). ", ctr);
  18. }
  19. public UInt32 Value
  20. {
  21. get { return ctr; }
  22. }
  23. public void Increment()
  24. {
  25. ctr += (uint) 1;
  26. }
  27. }
  28. public class NonZeroCounter : Counter
  29. {
  30. public NonZeroCounter(int startIndex) : this((uint) startIndex)
  31. {
  32. }
  33. private NonZeroCounter(UInt32 startIndex) : base(startIndex)
  34. {
  35. }
  36. }
  37. // Compilation produces a compiler warning like the following:
  38. // Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
  39. // CLS-compliant
  40. // Type3.cs(7,14): (Location of symbol related to previous warning)
  1. <Assembly: CLSCompliant(True)>
  2. <CLSCompliant(False)> _
  3. Public Class Counter
  4. Dim ctr As UInt32
  5. Public Sub New
  6. ctr = 0
  7. End Sub
  8. Protected Sub New(ctr As UInt32)
  9. ctr = ctr
  10. End Sub
  11. Public Overrides Function ToString() As String
  12. Return String.Format("{0}). ", ctr)
  13. End Function
  14. Public ReadOnly Property Value As UInt32
  15. Get
  16. Return ctr
  17. End Get
  18. End Property
  19. Public Sub Increment()
  20. ctr += CType(1, UInt32)
  21. End Sub
  22. End Class
  23. Public Class NonZeroCounter : Inherits Counter
  24. Public Sub New(startIndex As Integer)
  25. MyClass.New(CType(startIndex, UInt32))
  26. End Sub
  27. Private Sub New(startIndex As UInt32)
  28. MyBase.New(CType(startIndex, UInt32))
  29. End Sub
  30. End Class
  31. ' Compilation produces a compiler warning like the following:
  32. ' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
  33. ' because it derives from 'Counter', which is not CLS-compliant.
  34. '
  35. ' Public Class NonZeroCounter : Inherits Counter
  36. ' ~~~~~~~~~~~~~~

成员签名中出现的所有类型(包括方法的返回类型或属性类型)必须符合 CLS。此外,对于泛型类型:

  • 构成实例化泛型类型的所有类型必须符合 CLS。

  • 所有用作针对泛型参数的约束的类型必须符合 CLS。

.NET Framework 通用类型系统包括大量直接受公共语言运行时支持且专以程序集元数据编码的内置类型。在这些内部类型中,下表中所列的类型都符合 CLS。

符合 CLS 的类型说明
Byte8 位无符号整数
Int1616 位带符号整数
Int3232 位带符号整数
Int6464 位带符号整数
Single单精度浮点值
Double双精度浮点值
Booleantruefalse 值类型
CharUTF 16 编码单元
Decimal非浮点十进制数字
IntPtr平台定义的大小的指针或句柄
String包含零个、一个或多个 Char 对象的集合

下表中所列的内部类型不符合 CLS。

不符合类型说明符合 CLS 的替代方法
SByte8 位带符号整数数据类型Int16
TypedReference指向对象及其运行时类型的指针None
UInt1616 位无符号整数Int32
UInt3232 位无符号整数Int64
UInt6464 位无符号整数Int64(可能溢出)、BigIntegerDouble
UIntPtr未签名的指针或句柄IntPtr

.NET Framework 类库或任何其他类库可能包含不符合 CLS 的其他类型;例如:

  • 装箱的值类型。下面的 C# 示例创建一个具有名为 intValue 类型的公共属性的类。由于 int 是一个装箱的值类型,因此编译器将其标记为不符合 CLS。
  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. public unsafe class TestClass
  4. {
  5. private int* val;
  6. public TestClass(int number)
  7. {
  8. val = (int*) number;
  9. }
  10. public int* Value {
  11. get { return val; }
  12. }
  13. }
  14. // The compiler generates the following output when compiling this example:
  15. // warning CS3003: Type of 'TestClass.Value' is not CLS-compliant
  • 类型化的引用是一个特殊构造,它包含一个对对象的引用和一个对类型的引用。类型化的引用由 TypedReference 类显示在 .NET Framework 中。

如果类型不符合 CLS,则需对其应用 CLSCompliantAttribute 值为 isCompliantfalse 特性。有关更多信息,请参阅 CLSCompliantAttribute 特性部分。

下面的示例说明了方法签名和泛型类型实例化中 CLS 遵从性的问题。它定义一个具有类型为 InvoiceItem 的属性和类型为 UInt32 的属性的 Nullable(Of UInt32) 类,以及一个具有类型为 UInt32Nullable(Of UInt32) 的参数的构造函数。您尝试编译此示例时会收到四个编译器警告。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class InvoiceItem
  4. {
  5. private uint invId = 0;
  6. private uint itemId = 0;
  7. private Nullable<uint> qty;
  8. public InvoiceItem(uint sku, Nullable<uint> quantity)
  9. {
  10. itemId = sku;
  11. qty = quantity;
  12. }
  13. public Nullable<uint> Quantity
  14. {
  15. get { return qty; }
  16. set { qty = value; }
  17. }
  18. public uint InvoiceId
  19. {
  20. get { return invId; }
  21. set { invId = value; }
  22. }
  23. }
  24. // The attempt to compile the example displays the following output:
  25. // Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
  26. // Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
  27. // Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
  28. // Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
  1. <Assembly: CLSCompliant(True)>
  2. Public Class InvoiceItem
  3. Private invId As UInteger = 0
  4. Private itemId As UInteger = 0
  5. Private qty AS Nullable(Of UInteger)
  6. Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
  7. itemId = sku
  8. qty = quantity
  9. End Sub
  10. Public Property Quantity As Nullable(Of UInteger)
  11. Get
  12. Return qty
  13. End Get
  14. Set
  15. qty = value
  16. End Set
  17. End Property
  18. Public Property InvoiceId As UInteger
  19. Get
  20. Return invId
  21. End Get
  22. Set
  23. invId = value
  24. End Set
  25. End Property
  26. End Class
  27. ' The attempt to compile the example displays output similar to the following:
  28. ' Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
  29. '
  30. ' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
  31. ' ~~~
  32. ' Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
  33. '
  34. ' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
  35. ' ~~~~~~~~
  36. ' Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
  37. '
  38. ' Public Property Quantity As Nullable(Of UInteger)
  39. ' ~~~~~~~~
  40. ' Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
  41. '
  42. ' Public Property InvoiceId As UInteger
  43. ' ~~~~~~~~~

若要消除编译器警告,请将 InvoiceItem 公共接口中不符合 CLS 的类型替换为符合 CLS 的类型:

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class InvoiceItem
  4. {
  5. private uint invId = 0;
  6. private uint itemId = 0;
  7. private Nullable<int> qty;
  8. public InvoiceItem(int sku, Nullable<int> quantity)
  9. {
  10. if (sku <= 0)
  11. throw new ArgumentOutOfRangeException("The item number is zero or negative.");
  12. itemId = (uint) sku;
  13. qty = quantity;
  14. }
  15. public Nullable<int> Quantity
  16. {
  17. get { return qty; }
  18. set { qty = value; }
  19. }
  20. public int InvoiceId
  21. {
  22. get { return (int) invId; }
  23. set {
  24. if (value <= 0)
  25. throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
  26. invId = (uint) value; }
  27. }
  28. }
  1. <Assembly: CLSCompliant(True)>
  2. Public Class InvoiceItem
  3. Private invId As UInteger = 0
  4. Private itemId As UInteger = 0
  5. Private qty AS Nullable(Of Integer)
  6. Public Sub New(sku As Integer, quantity As Nullable(Of Integer))
  7. If sku <= 0 Then
  8. Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
  9. End If
  10. itemId = CUInt(sku)
  11. qty = quantity
  12. End Sub
  13. Public Property Quantity As Nullable(Of Integer)
  14. Get
  15. Return qty
  16. End Get
  17. Set
  18. qty = value
  19. End Set
  20. End Property
  21. Public Property InvoiceId As Integer
  22. Get
  23. Return CInt(invId)
  24. End Get
  25. Set
  26. invId = CUInt(value)
  27. End Set
  28. End Property
  29. End Class

除了列出的特定类型之外,某些类型的类别也不符合 CLS。其中包括非托管指针类型和函数指针类型。下面的示例将生成一个编译器警告,因为它使用指向整数的指针来创建整数数组。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class ArrayHelper
  4. {
  5. unsafe public static Array CreateInstance(Type type, int* ptr, int items)
  6. {
  7. Array arr = Array.CreateInstance(type, items);
  8. int* addr = ptr;
  9. for (int ctr = 0; ctr < items; ctr++) {
  10. int value = *addr;
  11. arr.SetValue(value, ctr);
  12. addr++;
  13. }
  14. return arr;
  15. }
  16. }
  17. // The attempt to compile this example displays the following output:
  18. // UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant

对于符合 CLS 的抽象类(即在 C# 中标记为 abstract 的累或在 Visual Basic 中标记为 MustInherit 的类),该类的所有成员也必须符合 CLS。

命名约定Naming conventions

由于一些编程语言不区分大小写,因此标识符(例如,命名空间、类型和成员的名称)必须不仅限于大小写不同。如果两个标识符的小写映射是相同的,则这两个标识符被视为等效。下面的 C# 示例定义两个公共类:Personperson由于它们只是大小写不同,因此 C# 编译器会将其标记为不符合 CLS。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Person : person
  4. {
  5. }
  6. public class person
  7. {
  8. }
  9. // Compilation produces a compiler warning like the following:
  10. // Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
  11. // only in case is not CLS-compliant
  12. // Naming1.cs(6,14): (Location of symbol related to previous warning)

编程语言识别符(例如,命名空间、类型和成员的名称)必须遵照 Unicode 标准 3.0、技术报告 15 和附件 7这表示:

  • 标识符的第一个字符可以是任何 Unicode 大写字母、小写字母、标题大小写字母、修饰符字母、其他字母或字母数字。有关 Unicode 字符类别的信息,请参阅 System.Globalization.UnicodeCategory 枚举。

  • 后继字符可以作为第一个字符来自任何类别,还可以包含无间隔标记、间距组合标记、十进制数字、连接器标点符号以及格式设置代码。

在比较标识符之前,应筛选格式设置代码,并将标识符转换为 Unicode 范式 C,因为单个字符可由多个 UTF 16 编码的代码单位表示。在 Unicode 范式 C 中生成相同代码单位的字符序列不符合 CLS。下列示例定义了一个名为 的属性(包含字符 ANGSTROM SIGN (U+212B))和另一个名为 Å 的属性(包含字符 LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5))。C# 和 Visual Basic 编译器都将源代码标记为不符合 CLS。

  1. public class Size
  2. {
  3. private double a1;
  4. private double a2;
  5. public double
  6. {
  7. get { return a1; }
  8. set { a1 = value; }
  9. }
  10. public double Å
  11. {
  12. get { return a2; }
  13. set { a2 = value; }
  14. }
  15. }
  16. // Compilation produces a compiler warning like the following:
  17. // Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
  18. // CLS-compliant
  19. // Naming2a.cs(10,18): (Location of symbol related to previous warning)
  20. // Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
  21. // CLS-compliant
  22. // Naming2a.cs(12,8): (Location of symbol related to previous warning)
  23. // Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
  24. // CLS-compliant
  25. // Naming2a.cs(13,8): (Location of symbol related to previous warning)
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Size
  3. Private a1 As Double
  4. Private a2 As Double
  5. Public Property Å As Double
  6. Get
  7. Return a1
  8. End Get
  9. Set
  10. a1 = value
  11. End Set
  12. End Property
  13. Public Property Å As Double
  14. Get
  15. Return a2
  16. End Get
  17. Set
  18. a2 = value
  19. End Set
  20. End Property
  21. End Class
  22. ' Compilation produces a compiler warning like the following:
  23. ' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
  24. ' with identical signatures.
  25. '
  26. ' Public Property Å As Double
  27. ' ~

特定范围内的成员名称(如程序集中的命名空间、命名空间中的类型或某类型中的成员)必须是唯一的,通过重载解析的名称除外。此要求比常规类型系统的要求更加严格,后者允许一个范围内的多个成员在作为不同类型的成员(例如,一个是方法,一个是字段时)时,可以具有相同的名称。具体而言,对于类型成员:

  • 字段和嵌套类型只能通过名称区分。

  • 具有相同名称的方法、属性和事件必须具有除返回类型不同之外的其他不同之处。

下面的示例说明了成员名称在其范围内必须是唯一的要求。它定义了一个名为 Converter 的类,该类包括四个名为 Conversion 的成员。其中三个是方法,一个是属性。包含 Int64 参数的方法具有唯一名称,但是,具有 Int32 参数的两个方法不是,因为返回值不被视为成员签名的一部分。由于属性不能具有与重载方法相同的名称,因此 Conversion 属性也违反了此要求。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Converter
  4. {
  5. public double Conversion(int number)
  6. {
  7. return (double) number;
  8. }
  9. public float Conversion(int number)
  10. {
  11. return (float) number;
  12. }
  13. public double Conversion(long number)
  14. {
  15. return (double) number;
  16. }
  17. public bool Conversion
  18. {
  19. get { return true; }
  20. }
  21. }
  22. // Compilation produces a compiler error like the following:
  23. // Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
  24. // 'Conversion' with the same parameter types
  25. // Naming3.cs(8,18): (Location of symbol related to previous error)
  26. // Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
  27. // 'Conversion'
  28. // Naming3.cs(8,18): (Location of symbol related to previous error)
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Converter
  3. Public Function Conversion(number As Integer) As Double
  4. Return CDbl(number)
  5. End Function
  6. Public Function Conversion(number As Integer) As Single
  7. Return CSng(number)
  8. End Function
  9. Public Function Conversion(number As Long) As Double
  10. Return CDbl(number)
  11. End Function
  12. Public ReadOnly Property Conversion As Boolean
  13. Get
  14. Return True
  15. End Get
  16. End Property
  17. End Class
  18. ' Compilation produces a compiler error like the following:
  19. ' Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double'
  20. ' and 'Public Function Conversion(number As Integer) As Single' cannot
  21. ' overload each other because they differ only by return types.
  22. '
  23. ' Public Function Conversion(number As Integer) As Double
  24. ' ~~~~~~~~~~
  25. ' Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function
  26. ' Conversion(number As Integer) As Single' in this class.
  27. '
  28. ' Public ReadOnly Property Conversion As Boolean
  29. ' ~~~~~~~~~~

各种语言都包含唯一关键字,因此面向公共语言运行时的语言还必须提供一些机制,以便引用与关键字相符的标识符(例如,类型名称)。例如,case 是 C# 和 Visual Basic 两者中的关键字。但是,下面的 Visual Basic 示例可以通过使用左大括号和右大括号将名为 case 的类从 case 关键字中消除。否则,该示例将生成错误消息“关键字无法用作标识符”,并且编译将失败。

  1. Public Class [case]
  2. Private _id As Guid
  3. Private name As String
  4. Public Sub New(name As String)
  5. _id = Guid.NewGuid()
  6. Me.name = name
  7. End Sub
  8. Public ReadOnly Property ClientName As String
  9. Get
  10. Return name
  11. End Get
  12. End Property
  13. End Class

下面的 C# 示例可以通过使用 case 符号从语言关键字中消除标识符来实例化 @ 类。如果没有该符号,C# 编译器会显示两条错误消息:“应为类型”和“无效表达式术语‘case’”。

  1. using System;
  2. public class Example
  3. {
  4. public static void Main()
  5. {
  6. @case c = new @case("John");
  7. Console.WriteLine(c.ClientName);
  8. }
  9. }

类型转换Type conversion

公共语言规范定义了两个转换运算符:

  • op_Implicit 用于扩大转换,不会丢失数据或精度。例如,Decimal 结构包含一个已重载的 op_Implicit 运算符,以将整数类型值和 Char 值转换为 Decimal 值。

  • op_Explicit 用于收缩可能导致丢失量级(将一个值转换为一个范围更小的值)或精度的转换。例如,Decimal 结构包含一个已重载的 op_Explicit 运算符,以将 DoubleSingle 值转换为 Decimal,并将 Decimal 值转换为整数值:DoubleSingleChar

但是,并非所有语言都支持运算符重载或定义自定义运算符。如果选择实现这些转换运算符,您还应提供另一种执行转换的方法。我们建议提供 FromXxxToXxx 方法。

下面的示例定义符合 CLS 的隐式和显式转换。它创建表示带符号的双精度浮点数字的 UDouble 类。它提供从 UDoubleDouble 的隐式转换和从 UDoubleSingle、从 DoubleUDouble 以及从 SingleUDouble 的显式转换。它还将 ToDouble 方法定义为隐式转换运算符的替代方法,并将 ToSingleFromDoubleFromSingle 方法定义为显式转换运算符的替代方法。

  1. using System;
  2. public struct UDouble
  3. {
  4. private double number;
  5. public UDouble(double value)
  6. {
  7. if (value < 0)
  8. throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
  9. number = value;
  10. }
  11. public UDouble(float value)
  12. {
  13. if (value < 0)
  14. throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
  15. number = value;
  16. }
  17. public static readonly UDouble MinValue = (UDouble) 0.0;
  18. public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;
  19. public static explicit operator Double(UDouble value)
  20. {
  21. return value.number;
  22. }
  23. public static implicit operator Single(UDouble value)
  24. {
  25. if (value.number > (double) Single.MaxValue)
  26. throw new InvalidCastException("A UDouble value is out of range of the Single type.");
  27. return (float) value.number;
  28. }
  29. public static explicit operator UDouble(double value)
  30. {
  31. if (value < 0)
  32. throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
  33. return new UDouble(value);
  34. }
  35. public static implicit operator UDouble(float value)
  36. {
  37. if (value < 0)
  38. throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
  39. return new UDouble(value);
  40. }
  41. public static Double ToDouble(UDouble value)
  42. {
  43. return (Double) value;
  44. }
  45. public static float ToSingle(UDouble value)
  46. {
  47. return (float) value;
  48. }
  49. public static UDouble FromDouble(double value)
  50. {
  51. return new UDouble(value);
  52. }
  53. public static UDouble FromSingle(float value)
  54. {
  55. return new UDouble(value);
  56. }
  57. }
  1. Public Structure UDouble
  2. Private number As Double
  3. Public Sub New(value As Double)
  4. If value < 0 Then
  5. Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
  6. End If
  7. number = value
  8. End Sub
  9. Public Sub New(value As Single)
  10. If value < 0 Then
  11. Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
  12. End If
  13. number = value
  14. End Sub
  15. Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)
  16. Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue
  17. Public Shared Widening Operator CType(value As UDouble) As Double
  18. Return value.number
  19. End Operator
  20. Public Shared Narrowing Operator CType(value As UDouble) As Single
  21. If value.number > CDbl(Single.MaxValue) Then
  22. Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
  23. End If
  24. Return CSng(value.number)
  25. End Operator
  26. Public Shared Narrowing Operator CType(value As Double) As UDouble
  27. If value < 0 Then
  28. Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
  29. End If
  30. Return New UDouble(value)
  31. End Operator
  32. Public Shared Narrowing Operator CType(value As Single) As UDouble
  33. If value < 0 Then
  34. Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
  35. End If
  36. Return New UDouble(value)
  37. End Operator
  38. Public Shared Function ToDouble(value As UDouble) As Double
  39. Return CType(value, Double)
  40. End Function
  41. Public Shared Function ToSingle(value As UDouble) As Single
  42. Return CType(value, Single)
  43. End Function
  44. Public Shared Function FromDouble(value As Double) As UDouble
  45. Return New UDouble(value)
  46. End Function
  47. Public Shared Function FromSingle(value As Single) As UDouble
  48. Return New UDouble(value)
  49. End Function
  50. End Structure

数组Arrays

符合 CLS 的数组符合以下规则:

  • 数组的所有维度必须具有零下限。下面的示例创建一个不符合 CLS 的数组,其下限为 1。请注意,无论是否存在 CLSCompliantAttribute 特性,编译器都不检测由 Numbers.GetTenPrimes 方法返回的数组是否符合 CLS。
  1. [assembly: CLSCompliant(true)]
  2. public class Numbers
  3. {
  4. public static Array GetTenPrimes()
  5. {
  6. Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1});
  7. arr.SetValue(1, 1);
  8. arr.SetValue(2, 2);
  9. arr.SetValue(3, 3);
  10. arr.SetValue(5, 4);
  11. arr.SetValue(7, 5);
  12. arr.SetValue(11, 6);
  13. arr.SetValue(13, 7);
  14. arr.SetValue(17, 8);
  15. arr.SetValue(19, 9);
  16. arr.SetValue(23, 10);
  17. return arr;
  18. }
  19. }
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Numbers
  3. Public Shared Function GetTenPrimes() As Array
  4. Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1})
  5. arr.SetValue(1, 1)
  6. arr.SetValue(2, 2)
  7. arr.SetValue(3, 3)
  8. arr.SetValue(5, 4)
  9. arr.SetValue(7, 5)
  10. arr.SetValue(11, 6)
  11. arr.SetValue(13, 7)
  12. arr.SetValue(17, 8)
  13. arr.SetValue(19, 9)
  14. arr.SetValue(23, 10)
  15. Return arr
  16. End Function
  17. End Class
  • 所有数组元素必须包括符合 CLS 的类型。下面的示例定义返回不符合 CLS 的数组的两种方法。第一个返回 UInt32 值的数组。第二个返回包括 ObjectInt32 值的 UInt32 数组。虽然编译器因第一个数组是 UInt32 类型而将其标识为不合规,但无法识别第二个包含不符合 CLS 元素的数组。
  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Numbers
  4. {
  5. public static UInt32[] GetTenPrimes()
  6. {
  7. uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u };
  8. return arr;
  9. }
  10. public static Object[] GetFivePrimes()
  11. {
  12. Object[] arr = { 1, 2, 3, 5u, 7u };
  13. return arr;
  14. }
  15. }
  16. // Compilation produces a compiler warning like the following:
  17. // Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not
  18. // CLS-compliant
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Numbers
  3. Public Shared Function GetTenPrimes() As UInt32()
  4. Return { 1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui }
  5. End Function
  6. Public Shared Function GetFivePrimes() As Object()
  7. Dim arr() As Object = { 1, 2, 3, 5ui, 7ui }
  8. Return arr
  9. End Function
  10. End Class
  11. ' Compilation produces a compiler warning like the following:
  12. ' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant.
  13. '
  14. ' Public Shared Function GetTenPrimes() As UInt32()
  15. ' ~~~~~~~~~~~~
  • 具有数组参数的方法的重载决策基于它们是否为数组及它们的元素类型。因此,以下对重载的 GetSquares 方法的定义符合 CLS。
  1. using System;
  2. using System.Numerics;
  3. [assembly: CLSCompliant(true)]
  4. public class Numbers
  5. {
  6. public static byte[] GetSquares(byte[] numbers)
  7. {
  8. byte[] numbersOut = new byte[numbers.Length];
  9. for (int ctr = 0; ctr < numbers.Length; ctr++) {
  10. int square = ((int) numbers[ctr]) * ((int) numbers[ctr]);
  11. if (square <= Byte.MaxValue)
  12. numbersOut[ctr] = (byte) square;
  13. // If there's an overflow, assign MaxValue to the corresponding
  14. // element.
  15. else
  16. numbersOut[ctr] = Byte.MaxValue;
  17. }
  18. return numbersOut;
  19. }
  20. public static BigInteger[] GetSquares(BigInteger[] numbers)
  21. {
  22. BigInteger[] numbersOut = new BigInteger[numbers.Length];
  23. for (int ctr = 0; ctr < numbers.Length; ctr++)
  24. numbersOut[ctr] = numbers[ctr] * numbers[ctr];
  25. return numbersOut;
  26. }
  27. }
  1. Imports System.Numerics
  2. <Assembly: CLSCompliant(True)>
  3. Public Module Numbers
  4. Public Function GetSquares(numbers As Byte()) As Byte()
  5. Dim numbersOut(numbers.Length - 1) As Byte
  6. For ctr As Integer = 0 To numbers.Length - 1
  7. Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr)))
  8. If square <= Byte.MaxValue Then
  9. numbersOut(ctr) = CByte(square)
  10. ' If there's an overflow, assign MaxValue to the corresponding
  11. ' element.
  12. Else
  13. numbersOut(ctr) = Byte.MaxValue
  14. End If
  15. Next
  16. Return numbersOut
  17. End Function
  18. Public Function GetSquares(numbers As BigInteger()) As BigInteger()
  19. Dim numbersOut(numbers.Length - 1) As BigInteger
  20. For ctr As Integer = 0 To numbers.Length - 1
  21. numbersOut(ctr) = numbers(ctr) * numbers(ctr)
  22. Next
  23. Return numbersOut
  24. End Function
  25. End Module

接口Interfaces

符合 CLS 的接口可以定义属性、事件和虚拟方法(没有实现的方法)。符合 CLS 的接口不能有:

  • 静态方法或静态字段。如果您在接口中定义静态成员,那么 C# 和 Visual Basic 编译器将生成编译器错误。

  • 字段。如果您在接口中定义字段,则 C# 和 Visual Basic 编译器将生成编译器错误。

  • 不符合 CLS 的方法。例如,下面的接口定义包括方法 INumber.GetUnsigned,该方法标记为不符合 CLS。此示例生成编译器警告。

  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. public interface INumber
  4. {
  5. int Length();
  6. [CLSCompliant(false)] ulong GetUnsigned();
  7. }
  8. // Attempting to compile the example displays output like the following:
  9. // Interface2.cs(8,32): warning CS3010: 'INumber.GetUnsigned()': CLS-compliant interfaces
  10. // must have only CLS-compliant members
  1. <Assembly: CLSCompliant(True)>
  2. Public Interface INumber
  3. Function Length As Integer
  4. <CLSCompliant(False)> Function GetUnsigned As ULong
  5. End Interface
  6. ' Attempting to compile the example displays output like the following:
  7. ' Interface2.vb(9) : warning BC40033: Non CLS-compliant 'function' is not allowed in a
  8. ' CLS-compliant interface.
  9. '
  10. ' <CLSCompliant(False)> Function GetUnsigned As ULong
  11. ' ~~~~~~~~~~~

由于存在此规则,因此符合 CLS 的类型不需要实现不符合 CLS 的成员。如果一个符合 CLS 的框架公开实现不符合 CLS 接口的类,则其还应提供所有不符合 CLS 的成员的具体实现。

符合 CLS 的语言编译器还必须允许类提供在多个接口中具有相同名称和签名的成员的单独实现。C# 和 Visual Basic 都支持显式接口实现以提供同名方法的不同实现。Visual Basic 还支持 Implements 关键字,可让您显式指定特定成员要实现的接口和成员。下面的示例通过定义一个将 TemperatureICelsius 接口作为显式接口实现的 IFahrenheit 类来说明此方案。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public interface IFahrenheit
  4. {
  5. decimal GetTemperature();
  6. }
  7. public interface ICelsius
  8. {
  9. decimal GetTemperature();
  10. }
  11. public class Temperature : ICelsius, IFahrenheit
  12. {
  13. private decimal _value;
  14. public Temperature(decimal value)
  15. {
  16. // We assume that this is the Celsius value.
  17. _value = value;
  18. }
  19. decimal IFahrenheit.GetTemperature()
  20. {
  21. return _value * 9 / 5 + 32;
  22. }
  23. decimal ICelsius.GetTemperature()
  24. {
  25. return _value;
  26. }
  27. }
  28. public class Example
  29. {
  30. public static void Main()
  31. {
  32. Temperature temp = new Temperature(100.0m);
  33. ICelsius cTemp = temp;
  34. IFahrenheit fTemp = temp;
  35. Console.WriteLine("Temperature in Celsius: {0} degrees",
  36. cTemp.GetTemperature());
  37. Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
  38. fTemp.GetTemperature());
  39. }
  40. }
  41. // The example displays the following output:
  42. // Temperature in Celsius: 100.0 degrees
  43. // Temperature in Fahrenheit: 212.0 degrees
  1. <Assembly: CLSCompliant(True)>
  2. Public Interface IFahrenheit
  3. Function GetTemperature() As Decimal
  4. End Interface
  5. Public Interface ICelsius
  6. Function GetTemperature() As Decimal
  7. End Interface
  8. Public Class Temperature : Implements ICelsius, IFahrenheit
  9. Private _value As Decimal
  10. Public Sub New(value As Decimal)
  11. ' We assume that this is the Celsius value.
  12. _value = value
  13. End Sub
  14. Public Function GetFahrenheit() As Decimal _
  15. Implements IFahrenheit.GetTemperature
  16. Return _value * 9 / 5 + 32
  17. End Function
  18. Public Function GetCelsius() As Decimal _
  19. Implements ICelsius.GetTemperature
  20. Return _value
  21. End Function
  22. End Class
  23. Module Example
  24. Public Sub Main()
  25. Dim temp As New Temperature(100.0d)
  26. Console.WriteLine("Temperature in Celsius: {0} degrees",
  27. temp.GetCelsius())
  28. Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
  29. temp.GetFahrenheit())
  30. End Sub
  31. End Module
  32. ' The example displays the following output:
  33. ' Temperature in Celsius: 100.0 degrees
  34. ' Temperature in Fahrenheit: 212.0 degrees

枚举Enumerations

符合 CLS 的枚举必须遵循下列规则:

  • 枚举的基础类型必须是符合 CLS 的内部整数(ByteInt16Int32Int64)。例如,下面的代码尝试定义基础类型为 UInt32 的枚举并生成编译器警告。
  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public enum Size : uint {
  4. Unspecified = 0,
  5. XSmall = 1,
  6. Small = 2,
  7. Medium = 3,
  8. Large = 4,
  9. XLarge = 5
  10. };
  11. public class Clothing
  12. {
  13. public string Name;
  14. public string Type;
  15. public string Size;
  16. }
  17. // The attempt to compile the example displays a compiler warning like the following:
  18. // Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant
  1. <Assembly: CLSCompliant(True)>
  2. Public Enum Size As UInt32
  3. Unspecified = 0
  4. XSmall = 1
  5. Small = 2
  6. Medium = 3
  7. Large = 4
  8. XLarge = 5
  9. End Enum
  10. Public Class Clothing
  11. Public Name As String
  12. Public Type As String
  13. Public Size As Size
  14. End Class
  15. ' The attempt to compile the example displays a compiler warning like the following:
  16. ' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant.
  17. '
  18. ' Public Enum Size As UInt32
  19. ' ~~~~
  • 枚举类型必须具有名为 Value__ 且标有 FieldAttributes.RTSpecialName 特性的单个实例字段。这使得您可以隐式引用字段值。

  • 枚举包括文本静态字段,该字段的类型与枚举本身的类型匹配。例如,如果您用 StateState.On 的值定义 State.Off 枚举,则 State.OnState.Off 都是文本静态字段,其类型为 State

  • 有两种枚举:

    • 一种表示一组互斥的命名整数值的枚举。这种类型的枚举由缺少 System.FlagsAttribute 自定义特性表示。

    • 一种表示可结合用来生成未命名值的一组位标志的枚举。这种类型的枚举由存在 System.FlagsAttribute 自定义特性表示。

有关详细信息,请参阅 Enum 结构的文档。

  • 枚举的值不限于其指定值的范围。换言之,枚举中的值的范围是其基础值的范围。您可以使用 Enum.IsDefined 方法来确定指定的值是否为枚举成员。

类型成员概述Type members in general

公共语言规范要求将所有字段和方法作为特定类的成员进行访问。因此,全局静态字段和方法(即,除类型外定义的静态字段或方法)不符合 CLS。如果您尝试在您的源代码中包括全局字段或方法,则 C# 和 Visual Basic 编译器都会生成编译器错误。

公共语言规范仅支持标准托管调用约定。它不支持非托管调用约定和带使用 varargs 关键字标记的变量参数列表的方法。对于符合标准托管调用约定的变量自变量列表,请使用 ParamArrayAttribute 特性或单个语言的实现,如 C# 中的 params 关键字和 Visual Basic 中的 ParamArray 关键字。

成员可访问性Member accessibility

重写继承成员不能更改该成员的可访问性。例如,无法在派生类中通过私有方法重写基类中的公共方法。有一个例外:由其他程序集中的类型重写的程序集中的 protected internal(在 C# 中)或 Protected Friend(在 Visual Basic 中)成员。在该示例中,重写的可访问性是 Protected

下面的示例说明了当 CLSCompliantAttribute 特性设置为 true,并且 Human(它是派生自 Animal 的类)尝试将 Species 属性的可访问性从公开更改为私有时生成的错误。如果该示例的可访问性更改为公共,则其编译成功。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Animal
  4. {
  5. private string _species;
  6. public Animal(string species)
  7. {
  8. _species = species;
  9. }
  10. public virtual string Species
  11. {
  12. get { return _species; }
  13. }
  14. public override string ToString()
  15. {
  16. return _species;
  17. }
  18. }
  19. public class Human : Animal
  20. {
  21. private string _name;
  22. public Human(string name) : base("Homo Sapiens")
  23. {
  24. _name = name;
  25. }
  26. public string Name
  27. {
  28. get { return _name; }
  29. }
  30. private override string Species
  31. {
  32. get { return base.Species; }
  33. }
  34. public override string ToString()
  35. {
  36. return _name;
  37. }
  38. }
  39. public class Example
  40. {
  41. public static void Main()
  42. {
  43. Human p = new Human("John");
  44. Console.WriteLine(p.Species);
  45. Console.WriteLine(p.ToString());
  46. }
  47. }
  48. // The example displays the following output:
  49. // error CS0621: 'Human.Species': virtual or abstract members cannot be private
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Animal
  3. Private _species As String
  4. Public Sub New(species As String)
  5. _species = species
  6. End Sub
  7. Public Overridable ReadOnly Property Species As String
  8. Get
  9. Return _species
  10. End Get
  11. End Property
  12. Public Overrides Function ToString() As String
  13. Return _species
  14. End Function
  15. End Class
  16. Public Class Human : Inherits Animal
  17. Private _name As String
  18. Public Sub New(name As String)
  19. MyBase.New("Homo Sapiens")
  20. _name = name
  21. End Sub
  22. Public ReadOnly Property Name As String
  23. Get
  24. Return _name
  25. End Get
  26. End Property
  27. Private Overrides ReadOnly Property Species As String
  28. Get
  29. Return MyBase.Species
  30. End Get
  31. End Property
  32. Public Overrides Function ToString() As String
  33. Return _name
  34. End Function
  35. End Class
  36. Public Module Example
  37. Public Sub Main()
  38. Dim p As New Human("John")
  39. Console.WriteLine(p.Species)
  40. Console.WriteLine(p.ToString())
  41. End Sub
  42. End Module
  43. ' The example displays the following output:
  44. ' 'Private Overrides ReadOnly Property Species As String' cannot override
  45. ' 'Public Overridable ReadOnly Property Species As String' because
  46. ' they have different access levels.
  47. '
  48. ' Private Overrides ReadOnly Property Species As String

如果某个成员是可访问的,则该成员签名中的类型必须是可访问的。例如,这意味着公共成员不能包含类型是私有的、受保护的或内部的参数。下面的示例说明了当 StringWrapper 类构造函数公开一个用于确定如何包装字符串值的内部 StringOperationType 枚举值时出现的编译器错误。

  1. using System;
  2. using System.Text;
  3. public class StringWrapper
  4. {
  5. string internalString;
  6. StringBuilder internalSB = null;
  7. bool useSB = false;
  8. public StringWrapper(StringOperationType type)
  9. {
  10. if (type == StringOperationType.Normal) {
  11. useSB = false;
  12. }
  13. else {
  14. useSB = true;
  15. internalSB = new StringBuilder();
  16. }
  17. }
  18. // The remaining source code...
  19. }
  20. internal enum StringOperationType { Normal, Dynamic }
  21. // The attempt to compile the example displays the following output:
  22. // error CS0051: Inconsistent accessibility: parameter type
  23. // 'StringOperationType' is less accessible than method
  24. // 'StringWrapper.StringWrapper(StringOperationType)'
  1. Imports System.Text
  2. <Assembly:CLSCompliant(True)>
  3. Public Class StringWrapper
  4. Dim internalString As String
  5. Dim internalSB As StringBuilder = Nothing
  6. Dim useSB As Boolean = False
  7. Public Sub New(type As StringOperationType)
  8. If type = StringOperationType.Normal Then
  9. useSB = False
  10. Else
  11. internalSB = New StringBuilder()
  12. useSB = True
  13. End If
  14. End Sub
  15. ' The remaining source code...
  16. End Class
  17. Friend Enum StringOperationType As Integer
  18. Normal = 0
  19. Dynamic = 1
  20. End Enum
  21. ' The attempt to compile the example displays the following output:
  22. ' error BC30909: 'type' cannot expose type 'StringOperationType'
  23. ' outside the project through class 'StringWrapper'.
  24. '
  25. ' Public Sub New(type As StringOperationType)
  26. ' ~~~~~~~~~~~~~~~~~~~

泛型类型和成员Generic types and members

嵌套类型拥有的泛型参数数目总是至少与封闭类型的一样多。它们按位置对应于封闭类型中的泛型参数。泛型类型还可以包括新的泛型参数。

一个包含类型及其嵌套类型的泛型类型参数之间的关系可能由各种语言的语法隐藏。在下面的示例中,泛型类型 Outer<T> 包含两个嵌套的类:Inner1AInner1B<U>对于每个类从 ToString 继承的 Object.ToString 方法的调用,表示每个嵌套的类包括其包含的类的类型参数。

  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. public class Outer<T>
  4. {
  5. T value;
  6. public Outer(T value)
  7. {
  8. this.value = value;
  9. }
  10. public class Inner1A : Outer<T>
  11. {
  12. public Inner1A(T value) : base(value)
  13. { }
  14. }
  15. public class Inner1B<U> : Outer<T>
  16. {
  17. U value2;
  18. public Inner1B(T value1, U value2) : base(value1)
  19. {
  20. this.value2 = value2;
  21. }
  22. }
  23. }
  24. public class Example
  25. {
  26. public static void Main()
  27. {
  28. var inst1 = new Outer<String>("This");
  29. Console.WriteLine(inst1);
  30. var inst2 = new Outer<String>.Inner1A("Another");
  31. Console.WriteLine(inst2);
  32. var inst3 = new Outer<String>.Inner1B<int>("That", 2);
  33. Console.WriteLine(inst3);
  34. }
  35. }
  36. // The example displays the following output:
  37. // Outer`1[System.String]
  38. // Outer`1+Inner1A[System.String]
  39. // Outer`1+Inner1B`1[System.String,System.Int32]
  1. <Assembly:CLSCompliant(True)>
  2. Public Class Outer(Of T)
  3. Dim value As T
  4. Public Sub New(value As T)
  5. Me.value = value
  6. End Sub
  7. Public Class Inner1A : Inherits Outer(Of T)
  8. Public Sub New(value As T)
  9. MyBase.New(value)
  10. End Sub
  11. End Class
  12. Public Class Inner1B(Of U) : Inherits Outer(Of T)
  13. Dim value2 As U
  14. Public Sub New(value1 As T, value2 As U)
  15. MyBase.New(value1)
  16. Me.value2 = value2
  17. End Sub
  18. End Class
  19. End Class
  20. Public Module Example
  21. Public Sub Main()
  22. Dim inst1 As New Outer(Of String)("This")
  23. Console.WriteLine(inst1)
  24. Dim inst2 As New Outer(Of String).Inner1A("Another")
  25. Console.WriteLine(inst2)
  26. Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)
  27. Console.WriteLine(inst3)
  28. End Sub
  29. End Module
  30. ' The example displays the following output:
  31. ' Outer`1[System.String]
  32. ' Outer`1+Inner1A[System.String]
  33. ' Outer`1+Inner1B`1[System.String,System.Int32]

泛型类型名称采用 namen 格式进行编码,其中 name 是类型名称, 是字符文本,而 n 是针对类型声明的参数数目,或对于嵌套泛型类型为最近引入的类型参数的数目。此泛型类型名称的编码主要对使用反射来访问库中符合 CLS 的泛型类型的开发人员很有用。

如果将约束应用于泛型类型,则任何用作约束的类型也必须符合 CLS。下面的示例定义一个名为 BaseClass 的不符合 CLS 的类和一个其类型参数必须派生自 BaseCollection 的名为 BaseClass 的泛型类。但由于 BaseClass 不符合 CLS,因此编译器会发出警告。

  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. [CLSCompliant(false)] public class BaseClass
  4. {}
  5. public class BaseCollection<T> where T : BaseClass
  6. {}
  7. // Attempting to compile the example displays the following output:
  8. // warning CS3024: Constraint type 'BaseClass' is not CLS-compliant
  1. <Assembly: CLSCompliant(True)>
  2. <CLSCompliant(False)> Public Class BaseClass
  3. End Class
  4. Public Class BaseCollection(Of T As BaseClass)
  5. End Class
  6. ' Attempting to compile the example displays the following output:
  7. ' warning BC40040: Generic parameter constraint type 'BaseClass' is not
  8. ' CLS-compliant.
  9. '
  10. ' Public Class BaseCollection(Of T As BaseClass)
  11. ' ~~~~~~~~~

如果泛型类型派生自泛型基本类型,则其必须重新声明所有约束,以确保也满足对基本类型的约束。下面的示例定义可表示任何数值类型的 Number<T>它还定义表示浮点值的 FloatingPoint<T> 类。但是,源代码无法编译,因为它未将 Number<T> 上(T 必须是值类型)的约束应用于 FloatingPoint<T>

  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. public class Number<T> where T : struct
  4. {
  5. // use Double as the underlying type, since its range is a superset of
  6. // the ranges of all numeric types except BigInteger.
  7. protected double number;
  8. public Number(T value)
  9. {
  10. try {
  11. this.number = Convert.ToDouble(value);
  12. }
  13. catch (OverflowException e) {
  14. throw new ArgumentException("value is too large.", e);
  15. }
  16. catch (InvalidCastException e) {
  17. throw new ArgumentException("The value parameter is not numeric.", e);
  18. }
  19. }
  20. public T Add(T value)
  21. {
  22. return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
  23. }
  24. public T Subtract(T value)
  25. {
  26. return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
  27. }
  28. }
  29. public class FloatingPoint<T> : Number<T>
  30. {
  31. public FloatingPoint(T number) : base(number)
  32. {
  33. if (typeof(float) == number.GetType() ||
  34. typeof(double) == number.GetType() ||
  35. typeof(decimal) == number.GetType())
  36. this.number = Convert.ToDouble(number);
  37. else
  38. throw new ArgumentException("The number parameter is not a floating-point number.");
  39. }
  40. }
  41. // The attempt to comple the example displays the following output:
  42. // error CS0453: The type 'T' must be a non-nullable value type in
  43. // order to use it as parameter 'T' in the generic type or method 'Number<T>'
  1. <Assembly:CLSCompliant(True)>
  2. Public Class Number(Of T As Structure)
  3. ' Use Double as the underlying type, since its range is a superset of
  4. ' the ranges of all numeric types except BigInteger.
  5. Protected number As Double
  6. Public Sub New(value As T)
  7. Try
  8. Me.number = Convert.ToDouble(value)
  9. Catch e As OverflowException
  10. Throw New ArgumentException("value is too large.", e)
  11. Catch e As InvalidCastException
  12. Throw New ArgumentException("The value parameter is not numeric.", e)
  13. End Try
  14. End Sub
  15. Public Function Add(value As T) As T
  16. Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
  17. End Function
  18. Public Function Subtract(value As T) As T
  19. Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
  20. End Function
  21. End Class
  22. Public Class FloatingPoint(Of T) : Inherits Number(Of T)
  23. Public Sub New(number As T)
  24. MyBase.New(number)
  25. If TypeOf number Is Single Or
  26. TypeOf number Is Double Or
  27. TypeOf number Is Decimal Then
  28. Me.number = Convert.ToDouble(number)
  29. Else
  30. throw new ArgumentException("The number parameter is not a floating-point number.")
  31. End If
  32. End Sub
  33. End Class
  34. ' The attempt to comple the example displays the following output:
  35. ' error BC32105: Type argument 'T' does not satisfy the 'Structure'
  36. ' constraint for type parameter 'T'.
  37. '
  38. ' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
  39. ' ~

如果将此约束添加到 FloatingPoint<T> 类中,则该示例成功编译。

  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. public class Number<T> where T : struct
  4. {
  5. // use Double as the underlying type, since its range is a superset of
  6. // the ranges of all numeric types except BigInteger.
  7. protected double number;
  8. public Number(T value)
  9. {
  10. try {
  11. this.number = Convert.ToDouble(value);
  12. }
  13. catch (OverflowException e) {
  14. throw new ArgumentException("value is too large.", e);
  15. }
  16. catch (InvalidCastException e) {
  17. throw new ArgumentException("The value parameter is not numeric.", e);
  18. }
  19. }
  20. public T Add(T value)
  21. {
  22. return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
  23. }
  24. public T Subtract(T value)
  25. {
  26. return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
  27. }
  28. }
  29. public class FloatingPoint<T> : Number<T> where T : struct
  30. {
  31. public FloatingPoint(T number) : base(number)
  32. {
  33. if (typeof(float) == number.GetType() ||
  34. typeof(double) == number.GetType() ||
  35. typeof(decimal) == number.GetType())
  36. this.number = Convert.ToDouble(number);
  37. else
  38. throw new ArgumentException("The number parameter is not a floating-point number.");
  39. }
  40. }
  1. <Assembly:CLSCompliant(True)>
  2. Public Class Number(Of T As Structure)
  3. ' Use Double as the underlying type, since its range is a superset of
  4. ' the ranges of all numeric types except BigInteger.
  5. Protected number As Double
  6. Public Sub New(value As T)
  7. Try
  8. Me.number = Convert.ToDouble(value)
  9. Catch e As OverflowException
  10. Throw New ArgumentException("value is too large.", e)
  11. Catch e As InvalidCastException
  12. Throw New ArgumentException("The value parameter is not numeric.", e)
  13. End Try
  14. End Sub
  15. Public Function Add(value As T) As T
  16. Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
  17. End Function
  18. Public Function Subtract(value As T) As T
  19. Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
  20. End Function
  21. End Class
  22. Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)
  23. Public Sub New(number As T)
  24. MyBase.New(number)
  25. If TypeOf number Is Single Or
  26. TypeOf number Is Double Or
  27. TypeOf number Is Decimal Then
  28. Me.number = Convert.ToDouble(number)
  29. Else
  30. throw new ArgumentException("The number parameter is not a floating-point number.")
  31. End If
  32. End Sub
  33. End Class

公共语言规范对嵌套类型和受保护成员规定了一个保守的按实例化模型。开放式泛型类型不能公开具有包含嵌套的、受保护的泛型类型的特定实例化的签名的字段或成员。扩大泛型基类或接口的特定实例化的非泛型类型不能公开带签名的字段或成员,此类字段或成员包含嵌套的、受保护的泛型类型的不同实例化。

下面的示例定义一个泛型类型 C1<T>(或 Visual Basic 中的 C1(Of T))和一个受保护的类 C1<T>.N(或 Visual Basic 中的 C1(Of T).N)。C1<T> 有两个方法:M1M2但是,M1 并不符合 CLS,因为它试图从 C1<T>(或 C1(Of T))返回一个 C1<int>.N(或 C1(Of Integer).N)对象。另一个名为 C2 的类派生自 C1<long>(或 C1(Of Long))。它有两个方法:M3M4M3 不符合 CLS,因为它尝试从 C1<int>.N 的子类中返回 C1(Of Integer).N(或 C1<long>)对象。请注意,语言编译器可具有更高限制。在此示例中,Visual Basic 在尝试编译 M4 时显示错误。

  1. using System;
  2. [assembly:CLSCompliant(true)]
  3. public class C1<T>
  4. {
  5. protected class N { }
  6. protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not
  7. // accessible from within C1<T> in all
  8. // languages
  9. protected void M2(C1<T>.N n) { } // CLS-compliant – C1<T>.N accessible
  10. // inside C1<T>
  11. }
  12. public class C2 : C1<long>
  13. {
  14. protected void M3(C1<int>.N n) { } // Not CLS-compliant – C1<int>.N is not
  15. // accessible in C2 (extends C1<long>)
  16. protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is
  17. // accessible in C2 (extends C1<long>)
  18. }
  19. // Attempting to compile the example displays output like the following:
  20. // Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
  21. // Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
  1. <Assembly:CLSCompliant(True)>
  2. Public Class C1(Of T)
  3. Protected Class N
  4. End Class
  5. Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
  6. ' accessible from within C1(Of T) in all
  7. End Sub ' languages
  8. Protected Sub M2(n As C1(Of T).N) ' CLS-compliant – C1(Of T).N accessible
  9. End Sub ' inside C1(Of T)
  10. End Class
  11. Public Class C2 : Inherits C1(Of Long)
  12. Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant – C1(Of Integer).N is not
  13. End Sub ' accessible in C2 (extends C1(Of Long))
  14. Protected Sub M4(n As C1(Of Long).N)
  15. End Sub
  16. End Class
  17. ' Attempting to compile the example displays output like the following:
  18. ' error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace
  19. ' '<Default>' through class 'C1'.
  20. '
  21. ' Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
  22. ' ~~~~~~~~~~~~~~~~
  23. ' error BC30389: 'C1(Of T).N' is not accessible in this context because
  24. ' it is 'Protected'.
  25. '
  26. ' Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant - C1(Of Integer).N is not
  27. '
  28. ' ~~~~~~~~~~~~~~~~
  29. '
  30. ' error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
  31. '
  32. ' Protected Sub M4(n As C1(Of Long).N)
  33. ' ~~~~~~~~~~~~~

构造函数Constructors

符合 CLS 的类和结构中的构造函数必须遵循下列规则:

  • 派生类的构造函数必须先调用其基类的实例构造函数,然后才能访问继承的实例数据。存在此要求是因为基类构造函数并不由它们的派生类继承。此规则不适用于不支持直接继承的结构。

通常,编译器独立地强制实施 CLS 遵从性的此规则,如下面的示例所示。它创建派生自 Doctor 类的 Person 类,但 Doctor 类无法调用 Person 类构造函数来初始化继承的实例字段。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class Person
  4. {
  5. private string fName, lName, _id;
  6. public Person(string firstName, string lastName, string id)
  7. {
  8. if (String.IsNullOrEmpty(firstName + lastName))
  9. throw new ArgumentNullException("Either a first name or a last name must be provided.");
  10. fName = firstName;
  11. lName = lastName;
  12. _id = id;
  13. }
  14. public string FirstName
  15. {
  16. get { return fName; }
  17. }
  18. public string LastName
  19. {
  20. get { return lName; }
  21. }
  22. public string Id
  23. {
  24. get { return _id; }
  25. }
  26. public override string ToString()
  27. {
  28. return String.Format("{0}{1}{2}", fName,
  29. String.IsNullOrEmpty(fName) ? "" : " ",
  30. lName);
  31. }
  32. }
  33. public class Doctor : Person
  34. {
  35. public Doctor(string firstName, string lastName, string id)
  36. {
  37. }
  38. public override string ToString()
  39. {
  40. return "Dr. " + base.ToString();
  41. }
  42. }
  43. // Attempting to compile the example displays output like the following:
  44. // ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0
  45. // arguments
  46. // ctor1.cs(10,11): (Location of symbol related to previous error)
  1. <Assembly: CLSCompliant(True)>
  2. Public Class Person
  3. Private fName, lName, _id As String
  4. Public Sub New(firstName As String, lastName As String, id As String)
  5. If String.IsNullOrEmpty(firstName + lastName) Then
  6. Throw New ArgumentNullException("Either a first name or a last name must be provided.")
  7. End If
  8. fName = firstName
  9. lName = lastName
  10. _id = id
  11. End Sub
  12. Public ReadOnly Property FirstName As String
  13. Get
  14. Return fName
  15. End Get
  16. End Property
  17. Public ReadOnly Property LastName As String
  18. Get
  19. Return lName
  20. End Get
  21. End Property
  22. Public ReadOnly Property Id As String
  23. Get
  24. Return _id
  25. End Get
  26. End Property
  27. Public Overrides Function ToString() As String
  28. Return String.Format("{0}{1}{2}", fName,
  29. If(String.IsNullOrEmpty(fName), "", " "),
  30. lName)
  31. End Function
  32. End Class
  33. Public Class Doctor : Inherits Person
  34. Public Sub New(firstName As String, lastName As String, id As String)
  35. End Sub
  36. Public Overrides Function ToString() As String
  37. Return "Dr. " + MyBase.ToString()
  38. End Function
  39. End Class
  40. ' Attempting to compile the example displays output like the following:
  41. ' Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call
  42. ' to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does
  43. ' not have an accessible 'Sub New' that can be called with no arguments.
  44. '
  45. ' Public Sub New()
  46. ' ~~~

属性Properties

符合 CLS 的类型的属性必须遵循下列规则:

  • 属性必须具有 setter 和/或 getter。在程序集中,这些作为特殊方法实现,这意味着它们将显示为单独的方法(getter 命名为 get_propertyname,setter 命名为 set_propertyname),且在程序集元数据中标记为 SpecialName。C# 和 Visual Basic 编译器会自动执行此规则,而无需应用 CLSCompliantAttribute 特性。

  • 属性的类型是属性 getter 的返回类型和 setter 的最后一个自变量。这些类型必须符合 CLS,并且不能通过引用将参数分配到属性中(即它们不能为托管指针)。

  • 如果属性包含 getter 和 setter 两者,则它们必须都是虚拟的、静态的或实例。C# 和 Visual Basic 编译器通过它们的属性定义语法自动执行此规则。

事件Events

事件由其名称和类型定义。事件类型是用于指示事件的委托。例如,AppDomain.AssemblyResolve 事件的类型为 ResolveEventHandler除事件本身外,带有基于事件名称的名称的三种方法提供事件的实现并在程序集的元数据中标记为 SpecialName

  • 用于添加事件处理程序的名为 add_EventName 的方法。例如,AppDomain.AssemblyResolve 事件的事件订阅方法名为 add_AssemblyResolve

  • 用于移除事件处理程序的名为 remove_EventName 的方法。例如,AppDomain.AssemblyResolve 事件的移除方法名为 remove_AssemblyResolve

  • 用于指示事件已发生的名为 raise_EventName 的方法。

备注

大多数关于事件的公共语言规范的规则都通过语言编译器实施,且对组件开发人员是透明的。

用于添加、移除和引发事件的方法必须拥有相同的可访问性。它们还必须都为静态、实例或虚拟的。用于添加和移除事件的方法具有一个类型为事件委托类型的参数。添加和移除方法必须同时存在或同时不存在。

如果两个读取之间的温度更改等于或超过阈值,则下面的示例将定义一个名为 Temperature 的类,该类会引发 TemperatureChanged 事件且符合 CLS。Temperature 类显式定义 raise_TemperatureChanged 方法,以便其可以有选择地执行事件处理程序。

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. [assembly: CLSCompliant(true)]
  5. public class TemperatureChangedEventArgs : EventArgs
  6. {
  7. private Decimal originalTemp;
  8. private Decimal newTemp;
  9. private DateTimeOffset when;
  10. public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)
  11. {
  12. originalTemp = original;
  13. newTemp = @new;
  14. when = time;
  15. }
  16. public Decimal OldTemperature
  17. {
  18. get { return originalTemp; }
  19. }
  20. public Decimal CurrentTemperature
  21. {
  22. get { return newTemp; }
  23. }
  24. public DateTimeOffset Time
  25. {
  26. get { return when; }
  27. }
  28. }
  29. public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);
  30. public class Temperature
  31. {
  32. private struct TemperatureInfo
  33. {
  34. public Decimal Temperature;
  35. public DateTimeOffset Recorded;
  36. }
  37. public event TemperatureChanged TemperatureChanged;
  38. private Decimal previous;
  39. private Decimal current;
  40. private Decimal tolerance;
  41. private List<TemperatureInfo> tis = new List<TemperatureInfo>();
  42. public Temperature(Decimal temperature, Decimal tolerance)
  43. {
  44. current = temperature;
  45. TemperatureInfo ti = new TemperatureInfo();
  46. ti.Temperature = temperature;
  47. tis.Add(ti);
  48. ti.Recorded = DateTimeOffset.UtcNow;
  49. this.tolerance = tolerance;
  50. }
  51. public Decimal CurrentTemperature
  52. {
  53. get { return current; }
  54. set {
  55. TemperatureInfo ti = new TemperatureInfo();
  56. ti.Temperature = value;
  57. ti.Recorded = DateTimeOffset.UtcNow;
  58. previous = current;
  59. current = value;
  60. if (Math.Abs(current - previous) >= tolerance)
  61. raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
  62. }
  63. }
  64. public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)
  65. {
  66. if (TemperatureChanged == null)
  67. return;
  68. foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {
  69. if (d.Method.Name.Contains("Duplicate"))
  70. Console.WriteLine("Duplicate event handler; event handler not executed.");
  71. else
  72. d.Invoke(this, eventArgs);
  73. }
  74. }
  75. }
  76. public class Example
  77. {
  78. public Temperature temp;
  79. public static void Main()
  80. {
  81. Example ex = new Example();
  82. }
  83. public Example()
  84. {
  85. temp = new Temperature(65, 3);
  86. temp.TemperatureChanged += this.TemperatureNotification;
  87. RecordTemperatures();
  88. Example ex = new Example(temp);
  89. ex.RecordTemperatures();
  90. }
  91. public Example(Temperature t)
  92. {
  93. temp = t;
  94. RecordTemperatures();
  95. }
  96. public void RecordTemperatures()
  97. {
  98. temp.TemperatureChanged += this.DuplicateTemperatureNotification;
  99. temp.CurrentTemperature = 66;
  100. temp.CurrentTemperature = 63;
  101. }
  102. internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)
  103. {
  104. Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature);
  105. }
  106. public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)
  107. {
  108. Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature);
  109. }
  110. }
  1. Imports System.Collections
  2. Imports System.Collections.Generic
  3. <Assembly: CLSCompliant(True)>
  4. Public Class TemperatureChangedEventArgs : Inherits EventArgs
  5. Private originalTemp As Decimal
  6. Private newTemp As Decimal
  7. Private [when] As DateTimeOffset
  8. Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)
  9. originalTemp = original
  10. newTemp = [new]
  11. [when] = [time]
  12. End Sub
  13. Public ReadOnly Property OldTemperature As Decimal
  14. Get
  15. Return originalTemp
  16. End Get
  17. End Property
  18. Public ReadOnly Property CurrentTemperature As Decimal
  19. Get
  20. Return newTemp
  21. End Get
  22. End Property
  23. Public ReadOnly Property [Time] As DateTimeOffset
  24. Get
  25. Return [when]
  26. End Get
  27. End Property
  28. End Class
  29. Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)
  30. Public Class Temperature
  31. Private Structure TemperatureInfo
  32. Dim Temperature As Decimal
  33. Dim Recorded As DateTimeOffset
  34. End Structure
  35. Public Event TemperatureChanged As TemperatureChanged
  36. Private previous As Decimal
  37. Private current As Decimal
  38. Private tolerance As Decimal
  39. Private tis As New List(Of TemperatureInfo)
  40. Public Sub New(temperature As Decimal, tolerance As Decimal)
  41. current = temperature
  42. Dim ti As New TemperatureInfo()
  43. ti.Temperature = temperature
  44. ti.Recorded = DateTimeOffset.UtcNow
  45. tis.Add(ti)
  46. Me.tolerance = tolerance
  47. End Sub
  48. Public Property CurrentTemperature As Decimal
  49. Get
  50. Return current
  51. End Get
  52. Set
  53. Dim ti As New TemperatureInfo
  54. ti.Temperature = value
  55. ti.Recorded = DateTimeOffset.UtcNow
  56. previous = current
  57. current = value
  58. If Math.Abs(current - previous) >= tolerance Then
  59. raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
  60. End If
  61. End Set
  62. End Property
  63. Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)
  64. If TemperatureChangedEvent Is Nothing Then Exit Sub
  65. Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()
  66. For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
  67. If d.Method.Name.Contains("Duplicate") Then
  68. Console.WriteLine("Duplicate event handler; event handler not executed.")
  69. Else
  70. d.Invoke(Me, eventArgs)
  71. End If
  72. Next
  73. End Sub
  74. End Class
  75. Public Class Example
  76. Public WithEvents temp As Temperature
  77. Public Shared Sub Main()
  78. Dim ex As New Example()
  79. End Sub
  80. Public Sub New()
  81. temp = New Temperature(65, 3)
  82. RecordTemperatures()
  83. Dim ex As New Example(temp)
  84. ex.RecordTemperatures()
  85. End Sub
  86. Public Sub New(t As Temperature)
  87. temp = t
  88. RecordTemperatures()
  89. End Sub
  90. Public Sub RecordTemperatures()
  91. temp.CurrentTemperature = 66
  92. temp.CurrentTemperature = 63
  93. End Sub
  94. Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
  95. Handles temp.TemperatureChanged
  96. Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
  97. End Sub
  98. Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
  99. Handles temp.TemperatureChanged
  100. Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
  101. End Sub
  102. End Class

OverloadsOverloads

公共语言规范对重载成员有下列要求:

  • 成员可以根据参数数量和任何参数的类型进行重载。在重载间进行区分时,不考虑应用于方法或其参数的调用约定、返回类型、自定义修饰符,也不考虑是按照值还是引用传递参数。有关示例,请参阅命名约定部分中名称在范围内必须是唯一的代码需求。

  • 只可重载属性和方法。无法重载字段和事件。

  • 泛型方法可以基于其泛型参数的数目进行重载。

备注

op_Explicitop_Implicit 运算符是返回值不被视为重载决策的方法签名的一部分的规则的例外情况。可以基于这两个运算符的参数和返回值对其进行重载。

异常Exceptions

异常对象必须从 System.Exception 派生或从派生自 System.Exception 的另一种类型派生。下面的示例说明了当名为 ErrorClass 的自定义类用于异常处理时产生的编译器错误。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class ErrorClass
  4. {
  5. string msg;
  6. public ErrorClass(string errorMessage)
  7. {
  8. msg = errorMessage;
  9. }
  10. public string Message
  11. {
  12. get { return msg; }
  13. }
  14. }
  15. public static class StringUtilities
  16. {
  17. public static string[] SplitString(this string value, int index)
  18. {
  19. if (index < 0 | index > value.Length) {
  20. ErrorClass badIndex = new ErrorClass("The index is not within the string.");
  21. throw badIndex;
  22. }
  23. string[] retVal = { value.Substring(0, index - 1),
  24. value.Substring(index) };
  25. return retVal;
  26. }
  27. }
  28. // Compilation produces a compiler error like the following:
  29. // Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
  30. // System.Exception
  1. Imports System.Runtime.CompilerServices
  2. <Assembly: CLSCompliant(True)>
  3. Public Class ErrorClass
  4. Dim msg As String
  5. Public Sub New(errorMessage As String)
  6. msg = errorMessage
  7. End Sub
  8. Public ReadOnly Property Message As String
  9. Get
  10. Return msg
  11. End Get
  12. End Property
  13. End Class
  14. Public Module StringUtilities
  15. <Extension()> Public Function SplitString(value As String, index As Integer) As String()
  16. If index < 0 Or index > value.Length Then
  17. Dim BadIndex As New ErrorClass("The index is not within the string.")
  18. Throw BadIndex
  19. End If
  20. Dim retVal() As String = { value.Substring(0, index - 1),
  21. value.Substring(index) }
  22. Return retVal
  23. End Function
  24. End Module
  25. ' Compilation produces a compiler error like the following:
  26. ' Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
  27. '
  28. ' Throw BadIndex
  29. ' ~~~~~~~~~~~~~~

若要更正此错误,ErrorClass 类必须继承自 System.Exception此外,必须重写 Message 属性。下面的示例更正这些错误以定义符合 CLS 的 ErrorClass 类。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. public class ErrorClass : Exception
  4. {
  5. string msg;
  6. public ErrorClass(string errorMessage)
  7. {
  8. msg = errorMessage;
  9. }
  10. public override string Message
  11. {
  12. get { return msg; }
  13. }
  14. }
  15. public static class StringUtilities
  16. {
  17. public static string[] SplitString(this string value, int index)
  18. {
  19. if (index < 0 | index > value.Length) {
  20. ErrorClass badIndex = new ErrorClass("The index is not within the string.");
  21. throw badIndex;
  22. }
  23. string[] retVal = { value.Substring(0, index - 1),
  24. value.Substring(index) };
  25. return retVal;
  26. }
  27. }
  1. Imports System.Runtime.CompilerServices
  2. <Assembly: CLSCompliant(True)>
  3. Public Class ErrorClass : Inherits Exception
  4. Dim msg As String
  5. Public Sub New(errorMessage As String)
  6. msg = errorMessage
  7. End Sub
  8. Public Overrides ReadOnly Property Message As String
  9. Get
  10. Return msg
  11. End Get
  12. End Property
  13. End Class
  14. Public Module StringUtilities
  15. <Extension()> Public Function SplitString(value As String, index As Integer) As String()
  16. If index < 0 Or index > value.Length Then
  17. Dim BadIndex As New ErrorClass("The index is not within the string.")
  18. Throw BadIndex
  19. End If
  20. Dim retVal() As String = { value.Substring(0, index - 1),
  21. value.Substring(index) }
  22. Return retVal
  23. End Function
  24. End Module

特性Attributes

在 .NET Framework 程序集中,自定义特性提供了一个可扩展机制,用于存储自定义特性和检索有关编程对象(如程序集、类型、成员和方法参数)的元数据。自定义特性必须从 System.Attribute 派生或从派生自 System.Attribute 的类型派生。

下面的示例与此规则冲突。它定义了不是从 NumericAttribute 派生的 System.Attribute 类。请注意,编译器错误仅当应用不符合 CLS 的特性时会出现,而在定义类时不会出现。

  1. using System;
  2. [assembly: CLSCompliant(true)]
  3. [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
  4. public class NumericAttribute
  5. {
  6. private bool _isNumeric;
  7. public NumericAttribute(bool isNumeric)
  8. {
  9. _isNumeric = isNumeric;
  10. }
  11. public bool IsNumeric
  12. {
  13. get { return _isNumeric; }
  14. }
  15. }
  16. [Numeric(true)] public struct UDouble
  17. {
  18. double Value;
  19. }
  20. // Compilation produces a compiler error like the following:
  21. // Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
  22. // Attribute1.cs(7,14): (Location of symbol related to previous error)
  1. <Assembly: CLSCompliant(True)>
  2. <AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
  3. Public Class NumericAttribute
  4. Private _isNumeric As Boolean
  5. Public Sub New(isNumeric As Boolean)
  6. _isNumeric = isNumeric
  7. End Sub
  8. Public ReadOnly Property IsNumeric As Boolean
  9. Get
  10. Return _isNumeric
  11. End Get
  12. End Property
  13. End Class
  14. <Numeric(True)> Public Structure UDouble
  15. Dim Value As Double
  16. End Structure
  17. ' Compilation produces a compiler error like the following:
  18. ' error BC31504: 'NumericAttribute' cannot be used as an attribute because it
  19. ' does not inherit from 'System.Attribute'.
  20. '
  21. ' <Numeric(True)> Public Structure UDouble
  22. ' ~~~~~~~~~~~~~

构造函数或符合 CLS 的特性的属性只能公开以下类型:

下面的示例定义了从 DescriptionAttribute 派生的 Attribute 类。类构造函数具有 Descriptor 类型的参数,因此,该类不符合 CLS。请注意,C# 编译器发出警告,但编译成功,而 Visual Basic 编译器既不发出警告也不报告错误。

  1. using System;
  2. [assembly:CLSCompliantAttribute(true)]
  3. public enum DescriptorType { type, member };
  4. public class Descriptor
  5. {
  6. public DescriptorType Type;
  7. public String Description;
  8. }
  9. [AttributeUsage(AttributeTargets.All)]
  10. public class DescriptionAttribute : Attribute
  11. {
  12. private Descriptor desc;
  13. public DescriptionAttribute(Descriptor d)
  14. {
  15. desc = d;
  16. }
  17. public Descriptor Descriptor
  18. { get { return desc; } }
  19. }
  20. // Attempting to compile the example displays output like the following:
  21. // warning CS3015: 'DescriptionAttribute' has no accessible
  22. // constructors which use only CLS-compliant types
  1. <Assembly:CLSCompliantAttribute(True)>
  2. Public Enum DescriptorType As Integer
  3. Type = 0
  4. Member = 1
  5. End Enum
  6. Public Class Descriptor
  7. Public Type As DescriptorType
  8. Public Description As String
  9. End Class
  10. <AttributeUsage(AttributeTargets.All)> _
  11. Public Class DescriptionAttribute : Inherits Attribute
  12. Private desc As Descriptor
  13. Public Sub New(d As Descriptor)
  14. desc = d
  15. End Sub
  16. Public ReadOnly Property Descriptor As Descriptor
  17. Get
  18. Return desc
  19. End Get
  20. End Property
  21. End Class

CLSCompliantAttribute 特性The CLSCompliantAttribute attribute

CLSCompliantAttribute 特性用于指示程序元素是否使用公共语言规范进行编译。CLSCompliantAttribute.CLSCompliantAttribute(Boolean) 构造函数包含一个所需参数(isCompliant),此参数指示该程序元素是否符合 CLS。

编译时,编译器检测到假定为符合 CLS 的不合规元素,并发出警告。编译器不会对显式声明为不符合标准的类型或成员发出警告。

组件开发人员可以通过两种方法使用 CLSCompliantAttribute 特性:

  • 定义由组件公开的公共接口的符合 CLS 的部件以及不符合 CLS 的部件。如果特性用于将特定程序元素标记为符合 CLS,则使用该特性可保证能通过面向 .NET Framework 的所有语言和工具访问这些元素。

  • 确保组件库的公共接口仅公开符合 CLS 的程序元素。如果元素不符合 CLS,则编译器通常会发出一个警告。

警告

在某些情况下,语言编译器执行符合 CLS 的规则,而不管是否使用 CLSCompliantAttribute 特性。例如,在接口中定义静态成员会违反 CLS 规则。就这一点而言,如果在接口中定义 static(在 C# 中)或 Shared(在 Visual Basic 中)成员,C# 和 Visual Basic 编译器都会显示错误消息,且无法编译应用。

CLSCompliantAttribute 特性标记为具有 AttributeUsageAttribute 值的 AttributeTargets.All 特性。利用此值,您可以将 CLSCompliantAttribute 特性应用于任何程序元素,包括程序集、模块、类型(类、结构、枚举、接口和委托)、类型成员(构造函数、方法、属性、字段和事件)、参数、泛型参数和返回值。但实际上,您只应将该特性应用于程序集、类型和类型成员。否则,编译器在库的公共接口中遇到不符合标准的参数、泛型参数或返回值时,将忽略此特性并继续生成编译器警告。

CLSCompliantAttribute 特性的值由包含的程序元素继承。例如,如果程序集标记为符合 CLS,则其类型也符合 CLS。如果类型标记为符合 CLS,则其嵌套的类型和成员也符合 CLS。

您可以通过将 CLSCompliantAttribute 特性应用到包含的编程元素来显式重写继承的遵从性。例如,可以使用具有 CLSCompliantAttribute 值为 isCompliantfalse 特性来定义符合标准的程序集中的不符合标准的类型,还可以使用 isCompliant 值为 true 的特性来定义不符合标准的程序集中的符合标准的类型。您还可以在符合标准的类型中定义不符合标准的成员。但是,不符合标准的类型无法拥有符合标准的成员,因此您无法使用 isCompliant 值为 true 的特性从一个不符合标准的类型重写继承。

在开发组件时,应始终使用 CLSCompliantAttribute 特性来指示您的程序集、其类型及其成员是否符合 CLS。

创建符合 CLS 的组件:

  • 使用 CLSCompliantAttribute 将程序集标记为符合 CLS。

  • 将程序集中不符合 CLS 的所有公开的类型标记为不符合标准。

  • 将符合 CLS 的类型中的所有公开的成员标记为不符合标准。

  • 为不符合 CLS 的成员提供符合 CLS 的替代项。

如果已成功标记所有不符合标准的类型和成员,您的编译器不会发出任何不符合警告。但是,您应指出哪些成员不符合 CLS 并在产品文档中列出其不符合 CLS 的替代项。

下面的示例使用 CLSCompliantAttribute 特性定义符合 CLS 的程序集和类型 CharacterUtilities,该类型具有两个不符合 CLS 的成员。由于这两个成员标记有 CLSCompliant(false) 特性,因此编译器不生成任何警告。该类还为两种方法提供符合 CLS 的替代项。通常,我们只向 ToUTF16 方法添加两个重载,以便提供符合 CLS 的替代项。但是,由于无法基于返回值重载这些方法,因此符合 CLS 的方法的名称不同于不符合标准的方法的名称。

  1. using System;
  2. using System.Text;
  3. [assembly:CLSCompliant(true)]
  4. public class CharacterUtilities
  5. {
  6. [CLSCompliant(false)] public static ushort ToUTF16(String s)
  7. {
  8. s = s.Normalize(NormalizationForm.FormC);
  9. return Convert.ToUInt16(s[0]);
  10. }
  11. [CLSCompliant(false)] public static ushort ToUTF16(Char ch)
  12. {
  13. return Convert.ToUInt16(ch);
  14. }
  15. // CLS-compliant alternative for ToUTF16(String).
  16. public static int ToUTF16CodeUnit(String s)
  17. {
  18. s = s.Normalize(NormalizationForm.FormC);
  19. return (int) Convert.ToUInt16(s[0]);
  20. }
  21. // CLS-compliant alternative for ToUTF16(Char).
  22. public static int ToUTF16CodeUnit(Char ch)
  23. {
  24. return Convert.ToInt32(ch);
  25. }
  26. public bool HasMultipleRepresentations(String s)
  27. {
  28. String s1 = s.Normalize(NormalizationForm.FormC);
  29. return s.Equals(s1);
  30. }
  31. public int GetUnicodeCodePoint(Char ch)
  32. {
  33. if (Char.IsSurrogate(ch))
  34. throw new ArgumentException("ch cannot be a high or low surrogate.");
  35. return Char.ConvertToUtf32(ch.ToString(), 0);
  36. }
  37. public int GetUnicodeCodePoint(Char[] chars)
  38. {
  39. if (chars.Length > 2)
  40. throw new ArgumentException("The array has too many characters.");
  41. if (chars.Length == 2) {
  42. if (! Char.IsSurrogatePair(chars[0], chars[1]))
  43. throw new ArgumentException("The array must contain a low and a high surrogate.");
  44. else
  45. return Char.ConvertToUtf32(chars[0], chars[1]);
  46. }
  47. else {
  48. return Char.ConvertToUtf32(chars.ToString(), 0);
  49. }
  50. }
  51. }
  1. Imports System.Text
  2. <Assembly:CLSCompliant(True)>
  3. Public Class CharacterUtilities
  4. <CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
  5. s = s.Normalize(NormalizationForm.FormC)
  6. Return Convert.ToUInt16(s(0))
  7. End Function
  8. <CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort
  9. Return Convert.ToUInt16(ch)
  10. End Function
  11. ' CLS-compliant alternative for ToUTF16(String).
  12. Public Shared Function ToUTF16CodeUnit(s As String) As Integer
  13. s = s.Normalize(NormalizationForm.FormC)
  14. Return CInt(Convert.ToInt16(s(0)))
  15. End Function
  16. ' CLS-compliant alternative for ToUTF16(Char).
  17. Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
  18. Return Convert.ToInt32(ch)
  19. End Function
  20. Public Function HasMultipleRepresentations(s As String) As Boolean
  21. Dim s1 As String = s.Normalize(NormalizationForm.FormC)
  22. Return s.Equals(s1)
  23. End Function
  24. Public Function GetUnicodeCodePoint(ch As Char) As Integer
  25. If Char.IsSurrogate(ch) Then
  26. Throw New ArgumentException("ch cannot be a high or low surrogate.")
  27. End If
  28. Return Char.ConvertToUtf32(ch.ToString(), 0)
  29. End Function
  30. Public Function GetUnicodeCodePoint(chars() As Char) As Integer
  31. If chars.Length > 2 Then
  32. Throw New ArgumentException("The array has too many characters.")
  33. End If
  34. If chars.Length = 2 Then
  35. If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
  36. Throw New ArgumentException("The array must contain a low and a high surrogate.")
  37. Else
  38. Return Char.ConvertToUtf32(chars(0), chars(1))
  39. End If
  40. Else
  41. Return Char.ConvertToUtf32(chars.ToString(), 0)
  42. End If
  43. End Function
  44. End Class

如果您开发的是应用程序而不是库(即,如果不公开可由其他应用程序开发人员使用的类型或成员),则只有在您的语言不支持程序元素时,您的应用程序使用的程序元素的 CLS 遵从性才会引起关注。在这种情况下,当您尝试使用不符合 CLS 的元素时,您的语言编译器将生成错误。

跨语言互操作性Cross-Language Interoperability

语言独立性可能有许多含义。语言独立性和与语言无关的组件一文讨论了其中一个含义,其涉及将用一种语言编写的类型无缝地用于用另一种语言编写的应用程序。本文介绍第二个含义,涉及将用多种语言编写的代码组合到一个 .NET Framework 程序集。

以下示例通过创建一个名为 Utilities.dll 的包含两个类(NumericLibStringLib)的类库演示了跨语言互操作性。NumericLib 类用 C# 编写类,StringLib 类用 Visual Basic 编写。以下是 StringUtil.vb 的源代码,该源代码在其 ToTitleCase 类中包含一个成员StringLib

  1. Imports System.Collections.Generic
  2. Imports System.Runtime.CompilerServices
  3. Public Module StringLib
  4. Private exclusions As List(Of String)
  5. Sub New()
  6. Dim words() As String = { "a", "an", "and", "of", "the" }
  7. exclusions = New List(Of String)
  8. exclusions.AddRange(words)
  9. End Sub
  10. <Extension()> _
  11. Public Function ToTitleCase(title As String) As String
  12. Dim words() As String = title.Split()
  13. Dim result As String = String.Empty
  14. For ctr As Integer = 0 To words.Length - 1
  15. Dim word As String = words(ctr)
  16. If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
  17. result += word.Substring(0, 1).ToUpper() + _
  18. word.Substring(1).ToLower()
  19. Else
  20. result += word.ToLower()
  21. End If
  22. If ctr <= words.Length - 1 Then
  23. result += " "
  24. End If
  25. Next
  26. Return result
  27. End Function
  28. End Module

以下是 NumberUtil.cs 的源代码,定义具有 NumericLibIsEven 两个成员的 NearZero 类。

  1. using System;
  2. public static class NumericLib
  3. {
  4. public static bool IsEven(this IConvertible number)
  5. {
  6. if (number is Byte ||
  7. number is SByte ||
  8. number is Int16 ||
  9. number is UInt16 ||
  10. number is Int32 ||
  11. number is UInt32 ||
  12. number is Int64)
  13. return Convert.ToInt64(number) % 2 == 0;
  14. else if (number is UInt64)
  15. return ((ulong) number) % 2 == 0;
  16. else
  17. throw new NotSupportedException("IsEven called for a non-integer value.");
  18. }
  19. public static bool NearZero(double number)
  20. {
  21. return Math.Abs(number) < .00001;
  22. }
  23. }

若要将两个类打包到单个程序集中,必须将它们编译到模块中。要将 Visual Basic 源代码文件编译到模块,请使用此命令:

  1. vbc /t:module StringUtil.vb

有关 Visual Basic 编译器的命令行语法的详细信息,请参阅从命令行生成

要将 C# 源代码文件编译到模块,请使用此命令:

  1. csc /t:module NumberUtil.cs

有关 C# 编译器的命令行语法的详细信息,请参阅在命令行上使用 csc.exe 生成

然后,可以使用链接器选项将两个模块编译到一个程序集:

  1. link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll

然后以下实例调用 NumericLib.NearZeroStringLib.ToTitleCase 方法。请注意 Visual Basic 代码和 C# 代码都能够访问这两个类中的方法。

  1. using System;
  2. public class Example
  3. {
  4. public static void Main()
  5. {
  6. Double dbl = 0.0 - Double.Epsilon;
  7. Console.WriteLine(NumericLib.NearZero(dbl));
  8. string s = "war and peace";
  9. Console.WriteLine(s.ToTitleCase());
  10. }
  11. }
  12. // The example displays the following output:
  13. // True
  14. // War and Peace
  1. Module Example
  2. Public Sub Main()
  3. Dim dbl As Double = 0.0 - Double.Epsilon
  4. Console.WriteLine(NumericLib.NearZero(dbl))
  5. Dim s As String = "war and peace"
  6. Console.WriteLine(s.ToTitleCase())
  7. End Sub
  8. End Module
  9. ' The example displays the following output:
  10. ' True
  11. ' War and Peace

要编译 Visual Basic 代码,请使用此命令:

  1. vbc example.vb /r:UtilityLib.dll

要使用 C# 进行编译,请将编译器的名称从 vbc 更改为 csc,将文件扩展名从 .vb 更改为 .cs:

  1. csc example.cs /r:UtilityLib.dll

请参阅See also