语言独立性和独立于语言的组件

.NET 独立于语言。 这意味着,作为开发人员,你可以使用面向 .NET 实现的多种语言之一(如 C#、F# 和 Visual Basic)进行开发。 你可以访问为 .NET 实现开发的类库的类型和成员,而无需知道它们最初编写的语言,而无需遵循任何原始语言的约定。 如果你是组件开发人员,则任何 .NET 应用都可以访问你的组件,而不考虑其语言。

注释

本文的第一部分讨论如何创建与语言无关的组件,即使用任何语言编写的应用可以使用的组件。 还可以从使用多种语言编写的源代码创建单个组件或应用;请参阅本文第二部分中 跨语言互作性

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

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

CLS 遵从性规则

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

注释

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

若要设计独立于语言的组件,只需将 CLS 符合性规则应用于组件的公共接口。 专用实现不一定符合规范。

重要

CLS 符合性规则仅适用于组件的公共接口,不适用于其专用实现。

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

using System;

[assembly: CLSCompliant(true)]

public class Person
{
   private UInt16 personAge = 0;

   public UInt16 Age
   { get { return personAge; } }
}
// The attempt to compile the example displays the following compiler warning:
//    Public1.cs(10,18): warning CS3003: Type of 'Person.Age' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class Person
    Private personAge As UInt16

    Public ReadOnly Property Age As UInt16
        Get
            Return personAge
        End Get
    End Property
End Class
' The attempt to compile the example displays the following compiler warning:
'    Public1.vb(9) : warning BC40027: Return type of function 'Age' is not CLS-compliant.
'    
'       Public ReadOnly Property Age As UInt16
'                                ~~~

可以通过将 Age 属性的类型从 UInt16 更改为 Int16(符合 CLS 的 16 位带符号整数),使 Person 类 CLS 兼容。 无需更改专用 personAge 字段的类型。

using System;

[assembly: CLSCompliant(true)]

public class Person
{
   private Int16 personAge = 0;

   public Int16 Age
   { get { return personAge; } }
}
<Assembly: CLSCompliant(True)>

Public Class Person
    Private personAge As UInt16

    Public ReadOnly Property Age As Int16
        Get
            Return CType(personAge, Int16)
        End Get
    End Property
End Class

库的公共接口包括以下内容:

  • 公共类的定义。

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

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

下表列出了 CLS 符合性规则。 规则的文本从 ECMA-335 标准:公共语言基础结构逐字获取,这是 Ecma International 2012 年版权。 以下部分提供了有关这些规则的更多详细信息。

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

子部分索引

类型和类型成员签名

System.Object 类型符合 CLS,是 .NET 类型系统中所有对象类型的基类型。 .NET 中的继承是隐式的(例如,String 类隐式继承自 Object 类)或显式(例如,CultureNotFoundException 类显式继承自 ArgumentException 类,该类显式继承自 Exception 类。 要使派生类型符合 CLS,其基类型也必须符合 CLS。

以下示例显示了一个派生类型,其基类型不符合 CLS。 它定义一个基 Counter 类,该类使用无符号 32 位整数作为计数器。 由于该类通过包装无符号整数来提供计数器功能,因此该类被标记为不符合 CLS。 因此,派生类(NonZeroCounter)也不符合 CLS。

using System;

[assembly: CLSCompliant(true)]

[CLSCompliant(false)]
public class Counter
{
   UInt32 ctr;

   public Counter()
   {
      ctr = 0;
   }

   protected Counter(UInt32 ctr)
   {
      this.ctr = ctr;
   }

   public override string ToString()
   {
      return String.Format("{0}). ", ctr);
   }

   public UInt32 Value
   {
      get { return ctr; }
   }

   public void Increment()
   {
      ctr += (uint) 1;
   }
}

public class NonZeroCounter : Counter
{
   public NonZeroCounter(int startIndex) : this((uint) startIndex)
   {
   }

   private NonZeroCounter(UInt32 startIndex) : base(startIndex)
   {
   }
}
// Compilation produces a compiler warning like the following:
//    Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
//            CLS-compliant
//    Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> _
Public Class Counter
    Dim ctr As UInt32

    Public Sub New
        ctr = 0
    End Sub

    Protected Sub New(ctr As UInt32)
        ctr = ctr
    End Sub

    Public Overrides Function ToString() As String
        Return String.Format("{0}). ", ctr)
    End Function

    Public ReadOnly Property Value As UInt32
        Get
            Return ctr
        End Get
    End Property

    Public Sub Increment()
        ctr += CType(1, UInt32)
    End Sub
End Class

Public Class NonZeroCounter : Inherits Counter
    Public Sub New(startIndex As Integer)
        MyClass.New(CType(startIndex, UInt32))
    End Sub

    Private Sub New(startIndex As UInt32)
        MyBase.New(CType(startIndex, UInt32))
    End Sub
End Class
' Compilation produces a compiler warning like the following:
'    Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant 
'    because it derives from 'Counter', which is not CLS-compliant.
'    
'    Public Class NonZeroCounter : Inherits Counter
'                 ~~~~~~~~~~~~~~

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

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

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

.NET 通用类型系统 包括许多内置类型,这些内置类型由公共语言运行时直接支持,并且专门编码在程序集的元数据中。 在这些固有类型中,下表中列出的类型符合 CLS。

符合 CLS 的类型 说明
字节 8 位无符号整数
Int16 16 位有符号整数
Int32 32 位有符号整数
Int64 64 位有符号整数
Half 半精度浮点值
单精度浮点值
双精度浮点值
布尔值 true 或 false 值类型
Char UTF-16 编码代码单元
十进制 非浮点十进制数
IntPtr 平台定义的大小的指针或句柄
字符串 零、一个或多个 Char 对象的集合

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

不符合类型 说明 符合 CLS 的替代方案
SByte 8 位有符号整数数据类型 Int16
UInt16 16 位无符号整数 Int32
UInt32 32 位无符号整数 Int64
UInt64 64 位无符号整数 Int64(可能溢出)、BigIntegerDouble
UIntPtr 未签名的指针或句柄 IntPtr

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

  • 装箱的值类型。 以下 C# 示例创建一个类,该类具有名为 Valueint* 类型的公共属性。 由于 int* 是一个装箱的值类型,因此编译器将其标记为不符合 CLS。

    using System;
    
    [assembly:CLSCompliant(true)]
    
    public unsafe class TestClass
    {
       private int* val;
    
       public TestClass(int number)
       {
          val = (int*) number;
       }
    
       public int* Value {
          get { return val; }
       }
    }
    // The compiler generates the following output when compiling this example:
    //        warning CS3003: Type of 'TestClass.Value' is not CLS-compliant
    
  • 类型化引用是包含对对象的引用和对类型的引用的特殊构造。 类型化引用由 TypedReference 类在 .NET 中表示。

如果类型不符合 CLS,则需对其应用 CLSCompliantAttribute 值为 isCompliantfalse 特性。 有关详细信息,请参阅 CLSCompliantAttribute 属性 章节。

以下示例演示了在方法签名和泛型类型实例化中与 CLS 符合性问题相关的问题。 它定义了一个 InvoiceItem 类,该类的类型为 UInt32、类型为 Nullable<UInt32>的属性,以及具有类型为 UInt32Nullable<UInt32>的参数的构造函数。 尝试编译此示例时,会收到四个编译器警告。

using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem
{
   private uint invId = 0;
   private uint itemId = 0;
   private Nullable<uint> qty;

   public InvoiceItem(uint sku, Nullable<uint> quantity)
   {
      itemId = sku;
      qty = quantity;
   }

   public Nullable<uint> Quantity
   {
      get { return qty; }
      set { qty = value; }
   }

   public uint InvoiceId
   {
      get { return invId; }
      set { invId = value; }
   }
}
// The attempt to compile the example displays the following output:
//    Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
//    Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
//    Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
//    Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

    Private invId As UInteger = 0
    Private itemId As UInteger = 0
    Private qty AS Nullable(Of UInteger)

    Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
        itemId = sku
        qty = quantity
    End Sub

    Public Property Quantity As Nullable(Of UInteger)
        Get
            Return qty
        End Get
        Set
            qty = value
        End Set
    End Property

    Public Property InvoiceId As UInteger
        Get
            Return invId
        End Get
        Set
            invId = value
        End Set
    End Property
End Class
' The attempt to compile the example displays output similar to the following:
'    Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
'    
'       Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
'                      ~~~
'    Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'    
'       Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
'                                                               ~~~~~~~~
'    Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'    
'       Public Property Quantity As Nullable(Of UInteger)
'                                               ~~~~~~~~
'    Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
'    
'       Public Property InvoiceId As UInteger
'                       ~~~~~~~~~

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

using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem
{
   private uint invId = 0;
   private uint itemId = 0;
   private Nullable<int> qty;

   public InvoiceItem(int sku, Nullable<int> quantity)
   {
      if (sku <= 0)
         throw new ArgumentOutOfRangeException("The item number is zero or negative.");
      itemId = (uint) sku;

      qty = quantity;
   }

   public Nullable<int> Quantity
   {
      get { return qty; }
      set { qty = value; }
   }

   public int InvoiceId
   {
      get { return (int) invId; }
      set {
         if (value <= 0)
            throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
         invId = (uint) value; }
   }
}
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

    Private invId As UInteger = 0
    Private itemId As UInteger = 0
    Private qty AS Nullable(Of Integer)

    Public Sub New(sku As Integer, quantity As Nullable(Of Integer))
        If sku <= 0 Then
            Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
        End If
        itemId = CUInt(sku)
        qty = quantity
    End Sub

    Public Property Quantity As Nullable(Of Integer)
        Get
            Return qty
        End Get
        Set
            qty = value
        End Set
    End Property

    Public Property InvoiceId As Integer
        Get
            Return CInt(invId)
        End Get
        Set
            invId = CUInt(value)
        End Set
    End Property
End Class

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

using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper
{
   unsafe public static Array CreateInstance(Type type, int* ptr, int items)
   {
      Array arr = Array.CreateInstance(type, items);
      int* addr = ptr;
      for (int ctr = 0; ctr < items; ctr++) {
          int value = *addr;
          arr.SetValue(value, ctr);
          addr++;
      }
      return arr;
   }
}
// The attempt to compile this example displays the following output:
//    UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant

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

命名约定

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

using System;

[assembly: CLSCompliant(true)]

public class Person : person
{
}

public class person
{
}
// Compilation produces a compiler warning like the following:
//    Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
//                       only in case is not CLS-compliant
//    Naming1.cs(6,14): (Location of symbol related to previous warning)

编程语言标识符(如命名空间、类型和成员的名称)必须符合 unicode 标准 。 这意味着:

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

  • 后续字符可以是作为第一个字符的任何类别,也可以包括非间距标记、间距组合标记、十进制数字、连接器标点符号和格式代码。

在比较标识符之前,应筛选掉格式代码并将标识符转换为 Unicode 规范化表单 C,因为单个字符可以由多个 UTF-16 编码的代码单元表示。 在 Unicode 规范化表单 C 中生成相同代码单元的字符序列不符合 CLS。 以下示例定义名为 的属性,该属性由字符 ANGSTROM SIGN (U+212B)和名为 Å的第二个属性组成,该属性由字符拉丁文大写字母 A WITH RING ABOVE (U+00C5) 组成。 C# 和 Visual Basic 编译器将源代码标记为不符合 CLS。

public class Size
{
   private double a1;
   private double a2;

   public double Å
   {
       get { return a1; }
       set { a1 = value; }
   }

   public double Å
   {
       get { return a2; }
       set { a2 = value; }
   }
}
// Compilation produces a compiler warning like the following:
//    Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
//            CLS-compliant
//    Naming2a.cs(10,18): (Location of symbol related to previous warning)
//    Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
//            CLS-compliant
//    Naming2a.cs(12,8): (Location of symbol related to previous warning)
//    Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
//            CLS-compliant
//    Naming2a.cs(13,8): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
Public Class Size
    Private a1 As Double
    Private a2 As Double

    Public Property Å As Double
        Get
            Return a1
        End Get
        Set
            a1 = value
        End Set
    End Property

    Public Property Å As Double
        Get
            Return a2
        End Get
        Set
            a2 = value
        End Set
    End Property
End Class
' Compilation produces a compiler warning like the following:
'    Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
'     with identical signatures.
'    
'       Public Property Å As Double
'                       ~

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

  • 字段和嵌套类型单独按名称进行区分。

  • 具有相同名称的方法、属性和事件必须不同于返回类型。

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

using System;

[assembly: CLSCompliant(true)]

public class Converter
{
   public double Conversion(int number)
   {
      return (double) number;
   }

   public float Conversion(int number)
   {
      return (float) number;
   }

   public double Conversion(long number)
   {
      return (double) number;
   }

   public bool Conversion
   {
      get { return true; }
   }
}
// Compilation produces a compiler error like the following:
//    Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
//            'Conversion' with the same parameter types
//    Naming3.cs(8,18): (Location of symbol related to previous error)
//    Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
//            'Conversion'
//    Naming3.cs(8,18): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Converter
    Public Function Conversion(number As Integer) As Double
        Return CDbl(number)
    End Function

    Public Function Conversion(number As Integer) As Single
        Return CSng(number)
    End Function

    Public Function Conversion(number As Long) As Double
        Return CDbl(number)
    End Function

    Public ReadOnly Property Conversion As Boolean
        Get
            Return True
        End Get
    End Property
End Class
' Compilation produces a compiler error like the following:
'    Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double' 
'                    and 'Public Function Conversion(number As Integer) As Single' cannot 
'                    overload each other because they differ only by return types.
'    
'       Public Function Conversion(number As Integer) As Double
'                       ~~~~~~~~~~
'    Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function 
'                     Conversion(number As Integer) As Single' in this class.
'    
'       Public ReadOnly Property Conversion As Boolean
'                                ~~~~~~~~~~

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

Public Class [case]
    Private _id As Guid
    Private name As String

    Public Sub New(name As String)
        _id = Guid.NewGuid()
        Me.name = name
    End Sub

    Public ReadOnly Property ClientName As String
        Get
            Return name
        End Get
    End Property
End Class

以下 C# 示例能够通过使用 @ 符号来实例化 case 类,以消除语言关键字中的标识符的歧义。 如果没有它,C# 编译器则会显示两条错误消息:“类型预期”和“无效的表达式术语 'case'”。

using System;

public class Example
{
   public static void Main()
   {
      @case c = new @case("John");
      Console.WriteLine(c.ClientName);
   }
}

类型转换

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

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

  • op_Explicit,用于执行可能导致数量级或精度损失的缩小转换(即将值转换为范围较小的值)。 例如,Decimal 结构包括重载的 op_Explicit 运算符,用于将 DoubleSingle 值转换为 Decimal,并将 Decimal 值转换为整型值、DoubleSingleChar

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

以下示例定义符合 CLS 的隐式转换和显式转换。 它创建一个 UDouble 类,该类表示无符号、双精度浮点数。 它提供从 UDoubleDouble 的隐式转换,以及从 UDoubleSingleDoubleUDoubleSingleUDouble的显式转换。 它还将 ToDouble 方法定义为隐式转换运算符和 ToSingleFromDoubleFromSingle 方法作为显式转换运算符的替代方法。

using System;

public struct UDouble
{
   private double number;

   public UDouble(double value)
   {
      if (value < 0)
         throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

      number = value;
   }

   public UDouble(float value)
   {
      if (value < 0)
         throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

      number = value;
   }

   public static readonly UDouble MinValue = (UDouble) 0.0;
   public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;

   public static explicit operator Double(UDouble value)
   {
      return value.number;
   }

   public static implicit operator Single(UDouble value)
   {
      if (value.number > (double) Single.MaxValue)
         throw new InvalidCastException("A UDouble value is out of range of the Single type.");

      return (float) value.number;
   }

   public static explicit operator UDouble(double value)
   {
      if (value < 0)
         throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

      return new UDouble(value);
   }

   public static implicit operator UDouble(float value)
   {
      if (value < 0)
         throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

      return new UDouble(value);
   }

   public static Double ToDouble(UDouble value)
   {
      return (Double) value;
   }

   public static float ToSingle(UDouble value)
   {
      return (float) value;
   }

   public static UDouble FromDouble(double value)
   {
      return new UDouble(value);
   }

   public static UDouble FromSingle(float value)
   {
      return new UDouble(value);
   }
}
Public Structure UDouble
    Private number As Double

    Public Sub New(value As Double)
        If value < 0 Then
            Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
        End If
        number = value
    End Sub

    Public Sub New(value As Single)
        If value < 0 Then
            Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
        End If
        number = value
    End Sub

    Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)
    Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue

    Public Shared Widening Operator CType(value As UDouble) As Double
        Return value.number
    End Operator

    Public Shared Narrowing Operator CType(value As UDouble) As Single
        If value.number > CDbl(Single.MaxValue) Then
            Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
        End If
        Return CSng(value.number)
    End Operator

    Public Shared Narrowing Operator CType(value As Double) As UDouble
        If value < 0 Then
            Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
        End If
        Return New UDouble(value)
    End Operator

    Public Shared Narrowing Operator CType(value As Single) As UDouble
        If value < 0 Then
            Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
        End If
        Return New UDouble(value)
    End Operator

    Public Shared Function ToDouble(value As UDouble) As Double
        Return CType(value, Double)
    End Function

    Public Shared Function ToSingle(value As UDouble) As Single
        Return CType(value, Single)
    End Function

    Public Shared Function FromDouble(value As Double) As UDouble
        Return New UDouble(value)
    End Function

    Public Shared Function FromSingle(value As Single) As UDouble
        Return New UDouble(value)
    End Function
End Structure

数组

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

  • 数组的所有维度都必须有零的下限。 以下示例创建一个不符合 CLS 的数组,其下限为 1。 尽管存在 CLSCompliantAttribute 属性,编译器不会检测到 Numbers.GetTenPrimes 方法返回的数组不符合 CLS。

    [assembly: CLSCompliant(true)]
    
    public class Numbers
    {
       public static Array GetTenPrimes()
       {
          Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1});
          arr.SetValue(1, 1);
          arr.SetValue(2, 2);
          arr.SetValue(3, 3);
          arr.SetValue(5, 4);
          arr.SetValue(7, 5);
          arr.SetValue(11, 6);
          arr.SetValue(13, 7);
          arr.SetValue(17, 8);
          arr.SetValue(19, 9);
          arr.SetValue(23, 10);
    
          return arr;
       }
    }
    
    <Assembly: CLSCompliant(True)>
    
    Public Class Numbers
        Public Shared Function GetTenPrimes() As Array
            Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1})
            arr.SetValue(1, 1)
            arr.SetValue(2, 2)
            arr.SetValue(3, 3)
            arr.SetValue(5, 4)
            arr.SetValue(7, 5)
            arr.SetValue(11, 6)
            arr.SetValue(13, 7)
            arr.SetValue(17, 8)
            arr.SetValue(19, 9)
            arr.SetValue(23, 10)
    
            Return arr
        End Function
    End Class
    
  • 所有数组元素都必须包含符合 CLS 的类型。 以下示例定义了两个返回不符合 CLS 的数组的方法。 第一个返回 UInt32 值的数组。 第二个返回包含 Int32UInt32 值的 Object 数组。 尽管编译器将第一个数组标识为不符合,因为它 UInt32 类型,但它无法识别第二个数组包含不符合 CLS 的元素。

    using System;
    
    [assembly: CLSCompliant(true)]
    
    public class Numbers
    {
       public static UInt32[] GetTenPrimes()
       {
          uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u };
          return arr;
       }
    
       public static Object[] GetFivePrimes()
       {
          Object[] arr = { 1, 2, 3, 5u, 7u };
          return arr;
       }
    }
    // Compilation produces a compiler warning like the following:
    //    Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not
    //            CLS-compliant
    
    <Assembly: CLSCompliant(True)>
    
    Public Class Numbers
        Public Shared Function GetTenPrimes() As UInt32()
            Return {1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui}
        End Function
    
        Public Shared Function GetFivePrimes() As Object()
            Dim arr() As Object = {1, 2, 3, 5ui, 7ui}
            Return arr
        End Function
    End Class
    ' Compilation produces a compiler warning like the following:
    '    warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant.
    '    
    '       Public Shared Function GetTenPrimes() As UInt32()
    '                              ~~~~~~~~~~~~
    
  • 具有数组参数的方法的重载解析基于它们是数组及其元素类型的事实。 因此,以下对重载的 GetSquares 方法的定义符合 CLS。

    using System;
    using System.Numerics;
    
    [assembly: CLSCompliant(true)]
    
    public class Numbers
    {
       public static byte[] GetSquares(byte[] numbers)
       {
          byte[] numbersOut = new byte[numbers.Length];
          for (int ctr = 0; ctr < numbers.Length; ctr++) {
             int square = ((int) numbers[ctr]) * ((int) numbers[ctr]);
             if (square <= Byte.MaxValue)
                numbersOut[ctr] = (byte) square;
             // If there's an overflow, assign MaxValue to the corresponding
             // element.
             else
                numbersOut[ctr] = Byte.MaxValue;
          }
          return numbersOut;
       }
    
       public static BigInteger[] GetSquares(BigInteger[] numbers)
       {
          BigInteger[] numbersOut = new BigInteger[numbers.Length];
          for (int ctr = 0; ctr < numbers.Length; ctr++)
             numbersOut[ctr] = numbers[ctr] * numbers[ctr];
    
          return numbersOut;
       }
    }
    
    Imports System.Numerics
    
    <Assembly: CLSCompliant(True)>
    
    Public Module Numbers
        Public Function GetSquares(numbers As Byte()) As Byte()
            Dim numbersOut(numbers.Length - 1) As Byte
            For ctr As Integer = 0 To numbers.Length - 1
                Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr)))
                If square <= Byte.MaxValue Then
                    numbersOut(ctr) = CByte(square)
                    ' If there's an overflow, assign MaxValue to the corresponding 
                    ' element.
                Else
                    numbersOut(ctr) = Byte.MaxValue
                End If
            Next
            Return numbersOut
        End Function
    
        Public Function GetSquares(numbers As BigInteger()) As BigInteger()
            Dim numbersOut(numbers.Length - 1) As BigInteger
            For ctr As Integer = 0 To numbers.Length - 1
                numbersOut(ctr) = numbers(ctr) * numbers(ctr)
            Next
            Return numbersOut
        End Function
    End Module
    

接口

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

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

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

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

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

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

符合 CLS 的语言编译器还必须允许类提供多个接口中具有相同名称和签名的成员的单独实现。 C# 和 Visual Basic 都支持 显式接口实现,以提供相同命名方法的不同实现。 Visual Basic 还支持 Implements 关键字,这使你能够显式指定特定成员实现的接口和成员。 以下示例通过定义一个将 ICelsiusIFahrenheit 接口实现为显式接口实现的 Temperature 类来演示此方案。

using System;

[assembly: CLSCompliant(true)]

public interface IFahrenheit
{
   decimal GetTemperature();
}

public interface ICelsius
{
   decimal GetTemperature();
}

public class Temperature : ICelsius, IFahrenheit
{
   private decimal _value;

   public Temperature(decimal value)
   {
      // We assume that this is the Celsius value.
      _value = value;
   }

   decimal IFahrenheit.GetTemperature()
   {
      return _value * 9 / 5 + 32;
   }

   decimal ICelsius.GetTemperature()
   {
      return _value;
   }
}
public class Example
{
   public static void Main()
   {
      Temperature temp = new Temperature(100.0m);
      ICelsius cTemp = temp;
      IFahrenheit fTemp = temp;
      Console.WriteLine($"Temperature in Celsius: {cTemp.GetTemperature()} degrees");
      Console.WriteLine($"Temperature in Fahrenheit: {fTemp.GetTemperature()} degrees");
   }
}
// The example displays the following output:
//       Temperature in Celsius: 100.0 degrees
//       Temperature in Fahrenheit: 212.0 degrees
<Assembly: CLSCompliant(True)>

Public Interface IFahrenheit
    Function GetTemperature() As Decimal
End Interface

Public Interface ICelsius
    Function GetTemperature() As Decimal
End Interface

Public Class Temperature : Implements ICelsius, IFahrenheit
    Private _value As Decimal

    Public Sub New(value As Decimal)
        ' We assume that this is the Celsius value.
        _value = value
    End Sub

    Public Function GetFahrenheit() As Decimal _
           Implements IFahrenheit.GetTemperature
        Return _value * 9 / 5 + 32
    End Function

    Public Function GetCelsius() As Decimal _
           Implements ICelsius.GetTemperature
        Return _value
    End Function
End Class

Module Example
    Public Sub Main()
        Dim temp As New Temperature(100.0d)
        Console.WriteLine("Temperature in Celsius: {0} degrees",
                          temp.GetCelsius())
        Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
                          temp.GetFahrenheit())
    End Sub
End Module
' The example displays the following output:
'       Temperature in Celsius: 100.0 degrees
'       Temperature in Fahrenheit: 212.0 degrees

枚举

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

  • 枚举的基础类型必须是符合 CLS 的内部整数(ByteInt16Int32Int64)。 例如,以下代码尝试定义一个枚举,其基础类型为 UInt32,并生成编译器警告。

    using System;
    
    [assembly: CLSCompliant(true)]
    
    public enum Size : uint {
       Unspecified = 0,
       XSmall = 1,
       Small = 2,
       Medium = 3,
       Large = 4,
       XLarge = 5
    };
    
    public class Clothing
    {
       public string Name;
       public string Type;
       public string Size;
    }
    // The attempt to compile the example displays a compiler warning like the following:
    //    Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant
    
    <Assembly: CLSCompliant(True)>
    
    Public Enum Size As UInt32
        Unspecified = 0
        XSmall = 1
        Small = 2
        Medium = 3
        Large = 4
        XLarge = 5
    End Enum
    
    Public Class Clothing
        Public Name As String
        Public Type As String
        Public Size As Size
    End Class
    ' The attempt to compile the example displays a compiler warning like the following:
    '    Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant.
    '    
    '    Public Enum Size As UInt32
    '                ~~~~
    
  • 枚举类型必须具有一个名为 Value__ 的实例字段,该字段标有 FieldAttributes.RTSpecialName 属性。 这使你可以隐式引用字段值。

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

  • 有两种类型的枚举:

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

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

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

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

类型成员概述

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

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

成员可访问性

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

以下示例演示了当 CLSCompliantAttribute 属性设置为 true时生成的错误,Human(派生自 Animal的类)尝试将 Species 属性的可访问性从公共更改为私有。 如果示例的可访问性更改为公共,则成功编译。

using System;

[assembly: CLSCompliant(true)]

public class Animal
{
   private string _species;

   public Animal(string species)
   {
      _species = species;
   }

   public virtual string Species
   {
      get { return _species; }
   }

   public override string ToString()
   {
      return _species;
   }
}

public class Human : Animal
{
   private string _name;

   public Human(string name) : base("Homo Sapiens")
   {
      _name = name;
   }

   public string Name
   {
      get { return _name; }
   }

   private override string Species
   {
      get { return base.Species; }
   }

   public override string ToString()
   {
      return _name;
   }
}

public class Example
{
   public static void Main()
   {
      Human p = new Human("John");
      Console.WriteLine(p.Species);
      Console.WriteLine(p.ToString());
   }
}
// The example displays the following output:
//    error CS0621: 'Human.Species': virtual or abstract members cannot be private
<Assembly: CLSCompliant(True)>

Public Class Animal
    Private _species As String

    Public Sub New(species As String)
        _species = species
    End Sub

    Public Overridable ReadOnly Property Species As String
        Get
            Return _species
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return _species
    End Function
End Class

Public Class Human : Inherits Animal
    Private _name As String

    Public Sub New(name As String)
        MyBase.New("Homo Sapiens")
        _name = name
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return _name
        End Get
    End Property

    Private Overrides ReadOnly Property Species As String
        Get
            Return MyBase.Species
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return _name
    End Function
End Class

Public Module Example
    Public Sub Main()
        Dim p As New Human("John")
        Console.WriteLine(p.Species)
        Console.WriteLine(p.ToString())
    End Sub
End Module
' The example displays the following output:
'     'Private Overrides ReadOnly Property Species As String' cannot override 
'     'Public Overridable ReadOnly Property Species As String' because
'      they have different access levels.
' 
'         Private Overrides ReadOnly Property Species As String

如果某个成员是可访问的,则该成员签名中的类型必须是可访问的。 例如,这意味着公共成员不能包含其类型为私有、受保护或内部的参数。 以下示例演示了当 StringWrapper 类构造函数公开内部 StringOperationType 枚举值以确定字符串值应该如何处理时所产生的编译器错误。

using System;
using System.Text;

public class StringWrapper
{
   string internalString;
   StringBuilder internalSB = null;
   bool useSB = false;

   public StringWrapper(StringOperationType type)
   {
      if (type == StringOperationType.Normal) {
         useSB = false;
      }
      else {
         useSB = true;
         internalSB = new StringBuilder();
      }
   }

   // The remaining source code...
}

internal enum StringOperationType { Normal, Dynamic }
// The attempt to compile the example displays the following output:
//    error CS0051: Inconsistent accessibility: parameter type
//            'StringOperationType' is less accessible than method
//            'StringWrapper.StringWrapper(StringOperationType)'
Imports System.Text

<Assembly: CLSCompliant(True)>

Public Class StringWrapper

    Dim internalString As String
    Dim internalSB As StringBuilder = Nothing
    Dim useSB As Boolean = False

    Public Sub New(type As StringOperationType)
        If type = StringOperationType.Normal Then
            useSB = False
        Else
            internalSB = New StringBuilder()
            useSB = True
        End If
    End Sub

    ' The remaining source code...
End Class

Friend Enum StringOperationType As Integer
    Normal = 0
    Dynamic = 1
End Enum
' The attempt to compile the example displays the following output:
'    error BC30909: 'type' cannot expose type 'StringOperationType'
'     outside the project through class 'StringWrapper'.
'    
'       Public Sub New(type As StringOperationType)
'                              ~~~~~~~~~~~~~~~~~~~

泛型类型和成员

嵌套类型始终具有与其封闭类型一样多的泛型参数。 这些参数对应于封闭类型中的泛型参数的位置。 泛型类型还可以包含新的泛型参数。

包含类型的泛型类型参数与其嵌套类型之间的关系可能由各个语言的语法隐藏。 在以下示例中,泛型类型 Outer<T> 包含两个嵌套类,Inner1AInner1B<U>。 对每个类从 Object.ToString()继承的 ToString 方法的调用表明,每个嵌套类都包含其包含类的类型参数。

using System;

[assembly:CLSCompliant(true)]

public class Outer<T>
{
   T value;

   public Outer(T value)
   {
      this.value = value;
   }

   public class Inner1A : Outer<T>
   {
      public Inner1A(T value) : base(value)
      {  }
   }

   public class Inner1B<U> : Outer<T>
   {
      U value2;

      public Inner1B(T value1, U value2) : base(value1)
      {
         this.value2 = value2;
      }
   }
}

public class Example
{
   public static void Main()
   {
      var inst1 = new Outer<String>("This");
      Console.WriteLine(inst1);

      var inst2 = new Outer<String>.Inner1A("Another");
      Console.WriteLine(inst2);

      var inst3 = new Outer<String>.Inner1B<int>("That", 2);
      Console.WriteLine(inst3);
   }
}
// The example displays the following output:
//       Outer`1[System.String]
//       Outer`1+Inner1A[System.String]
//       Outer`1+Inner1B`1[System.String,System.Int32]
<Assembly: CLSCompliant(True)>

Public Class Outer(Of T)
    Dim value As T

    Public Sub New(value As T)
        Me.value = value
    End Sub

    Public Class Inner1A : Inherits Outer(Of T)
        Public Sub New(value As T)
            MyBase.New(value)
        End Sub
    End Class

    Public Class Inner1B(Of U) : Inherits Outer(Of T)
        Dim value2 As U

        Public Sub New(value1 As T, value2 As U)
            MyBase.New(value1)
            Me.value2 = value2
        End Sub
    End Class
End Class

Public Module Example
    Public Sub Main()
        Dim inst1 As New Outer(Of String)("This")
        Console.WriteLine(inst1)

        Dim inst2 As New Outer(Of String).Inner1A("Another")
        Console.WriteLine(inst2)

        Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)
        Console.WriteLine(inst3)
    End Sub
End Module
' The example displays the following output:
'       Outer`1[System.String]
'       Outer`1+Inner1A[System.String]
'       Outer`1+Inner1B`1[System.String,System.Int32]

泛型类型名称的编码形式为 名称'n,其中 名称 是类型名称,'是字符文本,n 是在类型上声明的参数数,或者,对于嵌套泛型类型,新引入的类型参数数。 这种泛型类型名称的编码方式主要对使用反射来访问库中 CLS 兼容的泛型类型的开发人员感兴趣。

如果约束应用于泛型类型,则用作约束的任何类型也必须符合 CLS。 以下示例定义了一个名为 BaseClass 的类,它不符合 CLS 规范,以及一个名为 BaseCollection 的泛型类,其类型参数必须从 BaseClass派生。 但由于 BaseClass 不符合 CLS,编译器会发出警告。

using System;

[assembly:CLSCompliant(true)]

[CLSCompliant(false)] public class BaseClass
{}

public class BaseCollection<T> where T : BaseClass
{}
// Attempting to compile the example displays the following output:
//    warning CS3024: Constraint type 'BaseClass' is not CLS-compliant
<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> Public Class BaseClass
End Class


Public Class BaseCollection(Of T As BaseClass)
End Class
' Attempting to compile the example displays the following output:
'    warning BC40040: Generic parameter constraint type 'BaseClass' is not 
'    CLS-compliant.
'    
'    Public Class BaseCollection(Of T As BaseClass)
'                                        ~~~~~~~~~

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

using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct
{
   // use Double as the underlying type, since its range is a superset of
   // the ranges of all numeric types except BigInteger.
   protected double number;

   public Number(T value)
   {
      try {
         this.number = Convert.ToDouble(value);
      }
      catch (OverflowException e) {
         throw new ArgumentException("value is too large.", e);
      }
      catch (InvalidCastException e) {
         throw new ArgumentException("The value parameter is not numeric.", e);
      }
   }

   public T Add(T value)
   {
      return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
   }

   public T Subtract(T value)
   {
      return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
   }
}

public class FloatingPoint<T> : Number<T>
{
   public FloatingPoint(T number) : base(number)
   {
      if (typeof(float) == number.GetType() ||
          typeof(double) == number.GetType() ||
          typeof(decimal) == number.GetType())
         this.number = Convert.ToDouble(number);
      else
         throw new ArgumentException("The number parameter is not a floating-point number.");
   }
}
// The attempt to compile the example displays the following output:
//       error CS0453: The type 'T' must be a non-nullable value type in
//               order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly: CLSCompliant(True)>

Public Class Number(Of T As Structure)
    ' Use Double as the underlying type, since its range is a superset of
    ' the ranges of all numeric types except BigInteger.
    Protected number As Double

    Public Sub New(value As T)
        Try
            Me.number = Convert.ToDouble(value)
        Catch e As OverflowException
            Throw New ArgumentException("value is too large.", e)
        Catch e As InvalidCastException
            Throw New ArgumentException("The value parameter is not numeric.", e)
        End Try
    End Sub

    Public Function Add(value As T) As T
        Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
    End Function

    Public Function Subtract(value As T) As T
        Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
    End Function
End Class

Public Class FloatingPoint(Of T) : Inherits Number(Of T)
    Public Sub New(number As T)
        MyBase.New(number)
        If TypeOf number Is Single Or
                 TypeOf number Is Double Or
                 TypeOf number Is Decimal Then
            Me.number = Convert.ToDouble(number)
        Else
            throw new ArgumentException("The number parameter is not a floating-point number.")
        End If
    End Sub
End Class
' The attempt to compile the example displays the following output:
'    error BC32105: Type argument 'T' does not satisfy the 'Structure'
'    constraint for type parameter 'T'.
'    
'    Public Class FloatingPoint(Of T) : Inherits Number(Of T)
'                                                          ~

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

using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct
{
   // use Double as the underlying type, since its range is a superset of
   // the ranges of all numeric types except BigInteger.
   protected double number;

   public Number(T value)
   {
      try {
         this.number = Convert.ToDouble(value);
      }
      catch (OverflowException e) {
         throw new ArgumentException("value is too large.", e);
      }
      catch (InvalidCastException e) {
         throw new ArgumentException("The value parameter is not numeric.", e);
      }
   }

   public T Add(T value)
   {
      return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
   }

   public T Subtract(T value)
   {
      return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
   }
}

public class FloatingPoint<T> : Number<T> where T : struct
{
   public FloatingPoint(T number) : base(number)
   {
      if (typeof(float) == number.GetType() ||
          typeof(double) == number.GetType() ||
          typeof(decimal) == number.GetType())
         this.number = Convert.ToDouble(number);
      else
         throw new ArgumentException("The number parameter is not a floating-point number.");
   }
}
<Assembly: CLSCompliant(True)>

Public Class Number(Of T As Structure)
    ' Use Double as the underlying type, since its range is a superset of
    ' the ranges of all numeric types except BigInteger.
    Protected number As Double

    Public Sub New(value As T)
        Try
            Me.number = Convert.ToDouble(value)
        Catch e As OverflowException
            Throw New ArgumentException("value is too large.", e)
        Catch e As InvalidCastException
            Throw New ArgumentException("The value parameter is not numeric.", e)
        End Try
    End Sub

    Public Function Add(value As T) As T
        Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
    End Function

    Public Function Subtract(value As T) As T
        Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
    End Function
End Class

Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)
    Public Sub New(number As T)
        MyBase.New(number)
        If TypeOf number Is Single Or
                 TypeOf number Is Double Or
                 TypeOf number Is Decimal Then
            Me.number = Convert.ToDouble(number)
        Else
            throw new ArgumentException("The number parameter is not a floating-point number.")
        End If
    End Sub
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<long>的子类返回 C1<int>.N(或 C1(Of Integer).N)对象。 语言编译器可能更具限制性。 在此示例中,Visual Basic 在尝试编译 M4时显示错误。

using System;

[assembly:CLSCompliant(true)]

public class C1<T>
{
   protected class N { }

   protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not
                                      // accessible from within C1<T> in all
                                      // languages
   protected void M2(C1<T>.N n) { }   // CLS-compliant – C1<T>.N accessible
                                      // inside C1<T>
}

public class C2 : C1<long>
{
   protected void M3(C1<int>.N n) { }  // Not CLS-compliant – C1<int>.N is not
                                       // accessible in C2 (extends C1<long>)

   protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is
                                       // accessible in C2 (extends C1<long>)
}
// Attempting to compile the example displays output like the following:
//       Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
//       Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class C1(Of T)
    Protected Class N
    End Class

    Protected Sub M1(n As C1(Of Integer).N)   ' Not CLS-compliant - C1<int>.N not
        ' accessible from within C1(Of T) in all
    End Sub                                   ' languages


    Protected Sub M2(n As C1(Of T).N)     ' CLS-compliant – C1(Of T).N accessible
    End Sub                               ' inside C1(Of T)
End Class

Public Class C2 : Inherits C1(Of Long)
    Protected Sub M3(n As C1(Of Integer).N)   ' Not CLS-compliant – C1(Of Integer).N is not
    End Sub                                   ' accessible in C2 (extends C1(Of Long))

    Protected Sub M4(n As C1(Of Long).N)
    End Sub
End Class
' Attempting to compile the example displays output like the following:
'    error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace 
'    '<Default>' through class 'C1'.
'    
'       Protected Sub M1(n As C1(Of Integer).N)   ' Not CLS-compliant - C1<int>.N not
'                             ~~~~~~~~~~~~~~~~
'    error BC30389: 'C1(Of T).N' is not accessible in this context because 
'    it is 'Protected'.
'    
'       Protected Sub M3(n As C1(Of Integer).N)   ' Not CLS-compliant - C1(Of Integer).N is not
'    
'                             ~~~~~~~~~~~~~~~~
'    
'    error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
'    
'       Protected Sub M4(n As C1(Of Long).N)  
'                             ~~~~~~~~~~~~~

构造函数

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

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

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

    using System;
    
    [assembly: CLSCompliant(true)]
    
    public class Person
    {
       private string fName, lName, _id;
    
       public Person(string firstName, string lastName, string id)
       {
          if (String.IsNullOrEmpty(firstName + lastName))
             throw new ArgumentNullException("Either a first name or a last name must be provided.");
    
          fName = firstName;
          lName = lastName;
          _id = id;
       }
    
       public string FirstName
       {
          get { return fName; }
       }
    
       public string LastName
       {
          get { return lName; }
       }
    
       public string Id
       {
          get { return _id; }
       }
    
       public override string ToString()
       {
          return String.Format("{0}{1}{2}", fName,
                               String.IsNullOrEmpty(fName) ?  "" : " ",
                               lName);
       }
    }
    
    public class Doctor : Person
    {
       public Doctor(string firstName, string lastName, string id)
       {
       }
    
       public override string ToString()
       {
          return "Dr. " + base.ToString();
       }
    }
    // Attempting to compile the example displays output like the following:
    //    ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0
    //            arguments
    //    ctor1.cs(10,11): (Location of symbol related to previous error)
    
    <Assembly: CLSCompliant(True)>
    
    Public Class Person
        Private fName, lName, _id As String
    
        Public Sub New(firstName As String, lastName As String, id As String)
            If String.IsNullOrEmpty(firstName + lastName) Then
                Throw New ArgumentNullException("Either a first name or a last name must be provided.")
            End If
    
            fName = firstName
            lName = lastName
            _id = id
        End Sub
    
        Public ReadOnly Property FirstName As String
            Get
                Return fName
            End Get
        End Property
    
        Public ReadOnly Property LastName As String
            Get
                Return lName
            End Get
        End Property
    
        Public ReadOnly Property Id As String
            Get
                Return _id
            End Get
        End Property
    
        Public Overrides Function ToString() As String
            Return String.Format("{0}{1}{2}", fName,
                                 If(String.IsNullOrEmpty(fName), "", " "),
                                 lName)
        End Function
    End Class
    
    Public Class Doctor : Inherits Person
        Public Sub New(firstName As String, lastName As String, id As String)
        End Sub
    
        Public Overrides Function ToString() As String
            Return "Dr. " + MyBase.ToString()
        End Function
    End Class
    ' Attempting to compile the example displays output like the following:
    '    Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call 
    '    to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does 
    '    not have an accessible 'Sub New' that can be called with no arguments.
    '    
    '       Public Sub New()
    '                  ~~~
    
  • 除了创建对象之外,无法调用对象构造函数。 此外,不能初始化对象两次。 例如,这意味着 Object.MemberwiseClone 和反序列化方法不得调用构造函数。

性能

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

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

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

  • 如果属性同时具有 getter 和 setter,那么它们必须同为虚拟的、同为静态的,或同为实例的。 C# 和 Visual Basic 编译器通过其属性定义语法自动强制实施此规则。

事件

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

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

  • 用于删除名为 EventName remove_的事件处理程序的方法。 例如,AppDomain.AssemblyResolve 事件的删除方法命名为 remove_AssemblyResolve

  • 一种用于指示事件已发生的方法,命名为 raise_EventName

注释

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

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

以下示例定义一个名为 Temperature 的 CLS 兼容类,如果两个读数之间的温度变化等于或超过阈值,则引发 TemperatureChanged 事件。 Temperature 类显式定义 raise_TemperatureChanged 方法,以便它可以有选择地执行事件处理程序。

using System;
using System.Collections;
using System.Collections.Generic;

[assembly: CLSCompliant(true)]

public class TemperatureChangedEventArgs : EventArgs
{
   private Decimal originalTemp;
   private Decimal newTemp;
   private DateTimeOffset when;

   public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)
   {
      originalTemp = original;
      newTemp = @new;
      when = time;
   }

   public Decimal OldTemperature
   {
      get { return originalTemp; }
   }

   public Decimal CurrentTemperature
   {
      get { return newTemp; }
   }

   public DateTimeOffset Time
   {
      get { return when; }
   }
}

public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);

public class Temperature
{
   private struct TemperatureInfo
   {
      public Decimal Temperature;
      public DateTimeOffset Recorded;
   }

   public event TemperatureChanged TemperatureChanged;

   private Decimal previous;
   private Decimal current;
   private Decimal tolerance;
   private List<TemperatureInfo> tis = new List<TemperatureInfo>();

   public Temperature(Decimal temperature, Decimal tolerance)
   {
      current = temperature;
      TemperatureInfo ti = new TemperatureInfo();
      ti.Temperature = temperature;
      tis.Add(ti);
      ti.Recorded = DateTimeOffset.UtcNow;
      this.tolerance = tolerance;
   }

   public Decimal CurrentTemperature
   {
      get { return current; }
      set {
         TemperatureInfo ti = new TemperatureInfo();
         ti.Temperature = value;
         ti.Recorded = DateTimeOffset.UtcNow;
         previous = current;
         current = value;
         if (Math.Abs(current - previous) >= tolerance)
            raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
      }
   }

   public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)
   {
      if (TemperatureChanged == null)
         return;

      foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {
         if (d.Method.Name.Contains("Duplicate"))
            Console.WriteLine("Duplicate event handler; event handler not executed.");
         else
            d.Invoke(this, eventArgs);
      }
   }
}

public class Example
{
   public Temperature temp;

   public static void Main()
   {
      Example ex = new Example();
   }

   public Example()
   {
      temp = new Temperature(65, 3);
      temp.TemperatureChanged += this.TemperatureNotification;
      RecordTemperatures();
      Example ex = new Example(temp);
      ex.RecordTemperatures();
   }

   public Example(Temperature t)
   {
      temp = t;
      RecordTemperatures();
   }

   public void RecordTemperatures()
   {
      temp.TemperatureChanged += this.DuplicateTemperatureNotification;
      temp.CurrentTemperature = 66;
      temp.CurrentTemperature = 63;
   }

   internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)
   {
      Console.WriteLine($"Notification 1: The temperature changed from {e.OldTemperature} to {e.CurrentTemperature}");
   }

   public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)
   {
      Console.WriteLine($"Notification 2: The temperature changed from {e.OldTemperature} to {e.CurrentTemperature}");
   }
}
Imports System.Collections
Imports System.Collections.Generic

<Assembly: CLSCompliant(True)>

Public Class TemperatureChangedEventArgs : Inherits EventArgs
    Private originalTemp As Decimal
    Private newTemp As Decimal
    Private [when] As DateTimeOffset

    Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)
        originalTemp = original
        newTemp = [new]
        [when] = [time]
    End Sub

    Public ReadOnly Property OldTemperature As Decimal
        Get
            Return originalTemp
        End Get
    End Property

    Public ReadOnly Property CurrentTemperature As Decimal
        Get
            Return newTemp
        End Get
    End Property

    Public ReadOnly Property [Time] As DateTimeOffset
        Get
            Return [when]
        End Get
    End Property
End Class

Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)

Public Class Temperature
    Private Structure TemperatureInfo
        Dim Temperature As Decimal
        Dim Recorded As DateTimeOffset
    End Structure

    Public Event TemperatureChanged As TemperatureChanged

    Private previous As Decimal
    Private current As Decimal
    Private tolerance As Decimal
    Private tis As New List(Of TemperatureInfo)

    Public Sub New(temperature As Decimal, tolerance As Decimal)
        current = temperature
        Dim ti As New TemperatureInfo()
        ti.Temperature = temperature
        ti.Recorded = DateTimeOffset.UtcNow
        tis.Add(ti)
        Me.tolerance = tolerance
    End Sub

    Public Property CurrentTemperature As Decimal
        Get
            Return current
        End Get
        Set
            Dim ti As New TemperatureInfo
            ti.Temperature = value
            ti.Recorded = DateTimeOffset.UtcNow
            previous = current
            current = value
            If Math.Abs(current - previous) >= tolerance Then
                raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
            End If
        End Set
    End Property

    Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)
        If TemperatureChangedEvent Is Nothing Then Exit Sub

        Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()
        For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
            If d.Method.Name.Contains("Duplicate") Then
                Console.WriteLine("Duplicate event handler; event handler not executed.")
            Else
                d.Invoke(Me, eventArgs)
            End If
        Next
    End Sub
End Class

Public Class Example
    Public WithEvents temp As Temperature

    Public Shared Sub Main()
        Dim ex As New Example()
    End Sub

    Public Sub New()
        temp = New Temperature(65, 3)
        RecordTemperatures()
        Dim ex As New Example(temp)
        ex.RecordTemperatures()
    End Sub

    Public Sub New(t As Temperature)
        temp = t
        RecordTemperatures()
    End Sub

    Public Sub RecordTemperatures()
        temp.CurrentTemperature = 66
        temp.CurrentTemperature = 63

    End Sub

    Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
           Handles temp.TemperatureChanged
        Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
    End Sub

    Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
           Handles temp.TemperatureChanged
        Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
    End Sub
End Class

重载

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

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

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

  • 可以根据泛型方法的泛型参数数量来重载这些方法。

注释

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

异常

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

using System;

[assembly: CLSCompliant(true)]

public class ErrorClass
{
   string msg;

   public ErrorClass(string errorMessage)
   {
      msg = errorMessage;
   }

   public string Message
   {
      get { return msg; }
   }
}

public static class StringUtilities
{
   public static string[] SplitString(this string value, int index)
   {
      if (index < 0 | index > value.Length) {
         ErrorClass badIndex = new ErrorClass("The index is not within the string.");
         throw badIndex;
      }
      string[] retVal = { value.Substring(0, index - 1),
                          value.Substring(index) };
      return retVal;
   }
}
// Compilation produces a compiler error like the following:
//    Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
//            System.Exception
Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass
    Dim msg As String

    Public Sub New(errorMessage As String)
        msg = errorMessage
    End Sub

    Public ReadOnly Property Message As String
        Get
            Return msg
        End Get
    End Property
End Class

Public Module StringUtilities
    <Extension()> Public Function SplitString(value As String, index As Integer) As String()
        If index < 0 Or index > value.Length Then
            Dim BadIndex As New ErrorClass("The index is not within the string.")
            Throw BadIndex
        End If
        Dim retVal() As String = {value.Substring(0, index - 1),
                                   value.Substring(index)}
        Return retVal
    End Function
End Module
' Compilation produces a compiler error like the following:
'    Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
'    
'             Throw BadIndex
'             ~~~~~~~~~~~~~~

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

using System;

[assembly: CLSCompliant(true)]

public class ErrorClass : Exception
{
   string msg;

   public ErrorClass(string errorMessage)
   {
      msg = errorMessage;
   }

   public override string Message
   {
      get { return msg; }
   }
}

public static class StringUtilities
{
   public static string[] SplitString(this string value, int index)
   {
      if (index < 0 | index > value.Length) {
         ErrorClass badIndex = new ErrorClass("The index is not within the string.");
         throw badIndex;
      }
      string[] retVal = { value.Substring(0, index - 1),
                          value.Substring(index) };
      return retVal;
   }
}
Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass : Inherits Exception
    Dim msg As String

    Public Sub New(errorMessage As String)
        msg = errorMessage
    End Sub

    Public Overrides ReadOnly Property Message As String
        Get
            Return msg
        End Get
    End Property
End Class

Public Module StringUtilities
    <Extension()> Public Function SplitString(value As String, index As Integer) As String()
        If index < 0 Or index > value.Length Then
            Dim BadIndex As New ErrorClass("The index is not within the string.")
            Throw BadIndex
        End If
        Dim retVal() As String = {value.Substring(0, index - 1),
                                   value.Substring(index)}
        Return retVal
    End Function
End Module

特性

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

以下示例违反了此规则。 它定义不从 System.Attribute派生的 NumericAttribute 类。 仅当应用不符合 CLS 的属性时,编译器错误才会生成,而不是在定义类时。

using System;

[assembly: CLSCompliant(true)]

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
public class NumericAttribute
{
   private bool _isNumeric;

   public NumericAttribute(bool isNumeric)
   {
      _isNumeric = isNumeric;
   }

   public bool IsNumeric
   {
      get { return _isNumeric; }
   }
}

[Numeric(true)] public struct UDouble
{
   double Value;
}
// Compilation produces a compiler error like the following:
//    Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
//    Attribute1.cs(7,14): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

<AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
Public Class NumericAttribute
    Private _isNumeric As Boolean

    Public Sub New(isNumeric As Boolean)
        _isNumeric = isNumeric
    End Sub

    Public ReadOnly Property IsNumeric As Boolean
        Get
            Return _isNumeric
        End Get
    End Property
End Class

<Numeric(True)> Public Structure UDouble
    Dim Value As Double
End Structure
' Compilation produces a compiler error like the following:
'    error BC31504: 'NumericAttribute' cannot be used as an attribute because it 
'    does not inherit from 'System.Attribute'.
'    
'    <Numeric(True)> Public Structure UDouble
'     ~~~~~~~~~~~~~

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

以下示例定义派生自 AttributeDescriptionAttribute 类。 类构造函数具有 Descriptor类型的参数,因此该类不符合 CLS。 C# 编译器发出警告但编译成功,而 Visual Basic 编译器不会发出警告或错误。

using System;

[assembly:CLSCompliantAttribute(true)]

public enum DescriptorType { type, member };

public class Descriptor
{
   public DescriptorType Type;
   public String Description;
}

[AttributeUsage(AttributeTargets.All)]
public class DescriptionAttribute : Attribute
{
   private Descriptor desc;

   public DescriptionAttribute(Descriptor d)
   {
      desc = d;
   }

   public Descriptor Descriptor
   { get { return desc; } }
}
// Attempting to compile the example displays output like the following:
//       warning CS3015: 'DescriptionAttribute' has no accessible
//               constructors which use only CLS-compliant types
<Assembly: CLSCompliantAttribute(True)>

Public Enum DescriptorType As Integer
    Type = 0
    Member = 1
End Enum

Public Class Descriptor
    Public Type As DescriptorType
    Public Description As String
End Class

<AttributeUsage(AttributeTargets.All)> _
Public Class DescriptionAttribute : Inherits Attribute
    Private desc As Descriptor

    Public Sub New(d As Descriptor)
        desc = d
    End Sub

    Public ReadOnly Property Descriptor As Descriptor
        Get
            Return desc
        End Get
    End Property
End Class

CLSCompliantAttribute 特性

CLSCompliantAttribute 属性用于指示程序元素是否符合公共语言规范。 CLSCompliantAttribute(Boolean) 构造函数包含单个必需参数,isCompliant,该参数指示程序元素是否符合 CLS。

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

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

  • 定义由组件公开的公共接口的符合 CLS 的部件以及不符合 CLS 的部件。 当属性用于将特定程序元素标记为符合 CLS 时,其使用可确保这些元素可从面向 .NET 的所有语言和工具访问。

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

警告

在某些情况下,无论是否使用 CLSCompliantAttribute 属性,语言编译器都会强制实施符合 CLS 的规则。 例如,在接口中定义静态成员违反了 CLS 规则。 在这方面,如果在接口中定义 static(在 C#中)或 Shared(在 Visual Basic)成员,C# 和 Visual Basic 编译器都会显示错误消息,并且无法编译应用。

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

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

您可以通过将 CLSCompliantAttribute 特性应用到包含的编程元素来显式重写继承的遵从性。 例如,可以使用 CLSCompliantAttribute 属性并将其 isCompliant 值设置为 false 来在合规程序集内定义不合规类型;同样,可以将该属性的 isCompliant 值设为 true 来在不合规程序集中定义合规类型。 您还可以在符合标准的类型中定义不符合标准的成员。 但是,不合规类型不能具有合规成员,因此不能将属性(其 isCompliant 值为 true)用于覆盖不合规类型的继承。

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

创建符合 CLS 的组件:

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

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

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

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

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

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

using System;
using System.Text;

[assembly:CLSCompliant(true)]

public class CharacterUtilities
{
   [CLSCompliant(false)] public static ushort ToUTF16(String s)
   {
      s = s.Normalize(NormalizationForm.FormC);
      return Convert.ToUInt16(s[0]);
   }

   [CLSCompliant(false)] public static ushort ToUTF16(Char ch)
   {
      return Convert.ToUInt16(ch);
   }

   // CLS-compliant alternative for ToUTF16(String).
   public static int ToUTF16CodeUnit(String s)
   {
      s = s.Normalize(NormalizationForm.FormC);
      return (int) Convert.ToUInt16(s[0]);
   }

   // CLS-compliant alternative for ToUTF16(Char).
   public static int ToUTF16CodeUnit(Char ch)
   {
      return Convert.ToInt32(ch);
   }

   public bool HasMultipleRepresentations(String s)
   {
      String s1 = s.Normalize(NormalizationForm.FormC);
      return s.Equals(s1);
   }

   public int GetUnicodeCodePoint(Char ch)
   {
      if (Char.IsSurrogate(ch))
         throw new ArgumentException("ch cannot be a high or low surrogate.");

      return Char.ConvertToUtf32(ch.ToString(), 0);
   }

   public int GetUnicodeCodePoint(Char[] chars)
   {
      if (chars.Length > 2)
         throw new ArgumentException("The array has too many characters.");

      if (chars.Length == 2) {
         if (! Char.IsSurrogatePair(chars[0], chars[1]))
            throw new ArgumentException("The array must contain a low and a high surrogate.");
         else
            return Char.ConvertToUtf32(chars[0], chars[1]);
      }
      else {
         return Char.ConvertToUtf32(chars.ToString(), 0);
      }
   }
}
Imports System.Text

<Assembly: CLSCompliant(True)>

Public Class CharacterUtilities
    <CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
        s = s.Normalize(NormalizationForm.FormC)
        Return Convert.ToUInt16(s(0))
    End Function

    <CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort
        Return Convert.ToUInt16(ch)
    End Function

    ' CLS-compliant alternative for ToUTF16(String).
    Public Shared Function ToUTF16CodeUnit(s As String) As Integer
        s = s.Normalize(NormalizationForm.FormC)
        Return CInt(Convert.ToInt16(s(0)))
    End Function

    ' CLS-compliant alternative for ToUTF16(Char).
    Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
        Return Convert.ToInt32(ch)
    End Function

    Public Function HasMultipleRepresentations(s As String) As Boolean
        Dim s1 As String = s.Normalize(NormalizationForm.FormC)
        Return s.Equals(s1)
    End Function

    Public Function GetUnicodeCodePoint(ch As Char) As Integer
        If Char.IsSurrogate(ch) Then
            Throw New ArgumentException("ch cannot be a high or low surrogate.")
        End If
        Return Char.ConvertToUtf32(ch.ToString(), 0)
    End Function

    Public Function GetUnicodeCodePoint(chars() As Char) As Integer
        If chars.Length > 2 Then
            Throw New ArgumentException("The array has too many characters.")
        End If
        If chars.Length = 2 Then
            If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
                Throw New ArgumentException("The array must contain a low and a high surrogate.")
            Else
                Return Char.ConvertToUtf32(chars(0), chars(1))
            End If
        Else
            Return Char.ConvertToUtf32(chars.ToString(), 0)
        End If
    End Function
End Class

如果您正在开发一个应用而不是一个库(也就是说,如果您没有公开其他应用开发人员可以调用的类型或成员),那么仅当您的编程语言不支持您的应用所使用的那些程序元素时,才需关心它们的 CLS(公共语言规范)合规性。 在这种情况下,当你尝试使用不符合 CLS 的元素时,语言编译器将生成错误。

跨语言互作性

语言独立性有一些可能的含义。 其中一种含义涉及到从使用一种语言编写的应用取用以另一种语言编写的类型。 第二个含义是本文的重点,它涉及将用多种语言编写的代码组合到单个 .NET 程序集中。

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

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module StringLib
    Private exclusions As List(Of String)

    Sub New()
        Dim words() As String = {"a", "an", "and", "of", "the"}
        exclusions = New List(Of String)
        exclusions.AddRange(words)
    End Sub

    <Extension()> _
    Public Function ToTitleCase(title As String) As String
        Dim words() As String = title.Split()
        Dim result As String = String.Empty

        For ctr As Integer = 0 To words.Length - 1
            Dim word As String = words(ctr)
            If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
                result += word.Substring(0, 1).ToUpper() + _
                          word.Substring(1).ToLower()
            Else
                result += word.ToLower()
            End If
            If ctr <= words.Length - 1 Then
                result += " "
            End If
        Next
        Return result
    End Function
End Module

下面是NumberUtil.cs的源代码,它定义了一个 NumericLib 类,该类具有两个成员,IsEvenNearZero

using System;

public static class NumericLib
{
   public static bool IsEven(this IConvertible number)
   {
      if (number is Byte ||
          number is SByte ||
          number is Int16 ||
          number is UInt16 ||
          number is Int32 ||
          number is UInt32 ||
          number is Int64)
         return Convert.ToInt64(number) % 2 == 0;
      else if (number is UInt64)
         return ((ulong) number) % 2 == 0;
      else
         throw new NotSupportedException("IsEven called for a non-integer value.");
   }

   public static bool NearZero(double number)
   {
      return Math.Abs(number) < .00001;
   }
}

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

vbc /t:module StringUtil.vb

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

若要将 C# 源代码文件编译为模块,请使用以下命令:

csc /t:module NumberUtil.cs

然后使用 链接器选项 将这两个模块链接成一个装配体。

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

以下示例随后调用 NumericLib.NearZeroStringLib.ToTitleCase 方法。 Visual Basic 代码和 C# 代码都可以访问这两个类中的方法。

using System;

public class Example
{
   public static void Main()
   {
      Double dbl = 0.0 - Double.Epsilon;
      Console.WriteLine(NumericLib.NearZero(dbl));

      string s = "war and peace";
      Console.WriteLine(s.ToTitleCase());
   }
}
// The example displays the following output:
//       True
//       War and Peace
Module Example
    Public Sub Main()
        Dim dbl As Double = 0.0 - Double.Epsilon
        Console.WriteLine(NumericLib.NearZero(dbl))

        Dim s As String = "war and peace"
        Console.WriteLine(s.ToTitleCase())
    End Sub
End Module
' The example displays the following output:
'       True
'       War and Peace

若要编译 Visual Basic 代码,请使用以下命令:

vbc example.vb /r:UtilityLib.dll

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

csc example.cs /r:UtilityLib.dll