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

.NET Framework 是独立于语言的。The .NET Framework is language independent. 这意味着,作为开发人员,您可以使用面向 .NET Framework 的多种语言(例如,C#、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual Basic、Visual COBOL 以及 Windows PowerShell)之一进行开发。This means that, as a developer, you can develop in one of the many languages that target the .NET Framework, such as C#, C++/CLI, Eiffel, F#, IronPython, IronRuby, PowerBuilder, Visual Basic, Visual COBOL, and Windows PowerShell. 您可以访问针对 .NET Framework 开发的类库的类型和成员,而不必了解它们的初始编写语言,也不必遵循任何原始语言的约定。You can access the types and members of class libraries developed for the .NET Framework without having to know the language in which they were originally written and without having to follow any of the original language's conventions. 如果您是组件开发人员,无论组件采用哪种语言,均可由任何 .NET Framework 应用程序访问。If you are a component developer, your component can be accessed by any .NET Framework app regardless of its language.

备注

本文的第一部分讨论如何创建独立于语言的组件(即以任何语言编写的应用均可以使用的组件)。This first part of this article discusses creating language-independent components—that is, components that can be consumed by apps that are written in any language. 还可以从用多种语言编写的源代码创建单个组件或应用;请参阅本文第二部分的跨语言互操作性You can also create a single component or app from source code written in multiple languages; see Cross-Language Interoperability in the second part of this article.

若要与使用任何语言编写的其他对象完全交互,对象必须只向调用方公开那些所有语言共有的功能。To fully interact with other objects written in any language, objects must expose to callers only those features that are common to all languages. 此组通用功能由公共语言规范 (CLS) 定义,后者是一组适用于生成的程序集的规则。This common set of features is defined by the Common Language Specification (CLS), which is a set of rules that apply to generated assemblies. 公共语言规范在 ECMA-335 标准:公共语言基础结构的第 I 部分的第 7 条至第 11 条中进行了定义。The Common Language Specification is defined in Partition I, Clauses 7 through 11 of the ECMA-335 Standard: Common Language Infrastructure.

如果你的组件符合公共语言规范,则保证其符合 CLS 并可通过支持 CLS 的任何编程语言编写的程序集中的代码对其进行访问。If your component conforms to the Common Language Specification, it is guaranteed to be CLS-compliant and can be accessed from code in assemblies written in any programming language that supports the CLS. 你可以通过将 CLSCompliantAttribute 特性应用于源代码来确定自己的组件在编译时是否符合公共语言规范。You can determine whether your component conforms to the Common Language Specification at compile time by applying the CLSCompliantAttribute attribute to your source code. 有关详细信息,请参阅 CLSCompliantAttribute 特性For more information, see The CLSCompliantAttribute attribute.

本文内容:In this article:

CLS 遵从性规则CLS compliance rules

本节讨论用于创建符合 CLS 的组件的规则。This section discusses the rules for creating a CLS-compliant component. 有关规则的完整列表,请参阅 ECMA-335 标准:公共语言基础结构的第 I 部分的第 7 条至第 11 条中进行了定义。For a complete list of rules, see Partition I, Clause 11 of the ECMA-335 Standard: Common Language Infrastructure.

备注

公共语言规范讨论 CLS 遵从性的每个规则,因为它应用于使用者(以编程方式访问符合 CLS 的组件的开发人员)、框架(使用语言编译器创建符合 CLS 的库的开发人员)和扩展人员(创建可创建符合 CLS 的组件的语言编译器或代码分析器等工具的开发人员)。The Common Language Specification discusses each rule for CLS compliance as it applies to consumers (developers who are programmatically accessing a component that is CLS-compliant), frameworks (developers who are using a language compiler to create CLS-compliant libraries), and extenders (developers who are creating a tool such as a language compiler or a code parser that creates CLS-compliant components). 本文重点介绍适用于框架的规则。This article focuses on the rules as they apply to frameworks. 但请注意,一些适用于扩展程序的规则也适用于使用 Reflection.Emit 创建的程序集。Note, though, that some of the rules that apply to extenders may also apply to assemblies that are created using Reflection.Emit.

若要设计独立于语言的组件,您只需将 CLS 遵从性的规则应用于组件的公共接口即可。To design a component that is language independent, you only need to apply the rules for CLS compliance to your component's public interface. 您的私有实现不必符合规范。Your private implementation does not have to conform to the specification.

重要

CLS 遵从性的规则仅适用于组件的公共接口,而非其私有实现。The rules for CLS compliance apply only to a component's public interface, not to its private implementation.

例如,Byte 之外的无符号整数不符合 CLS。For example, unsigned integers other than Byte are not CLS-compliant. 由于以下示例中的 Person 类公开了 Age 类型的 UInt16 属性,因此以下代码显示编译器警告。Because the Person class in the following example exposes an Age property of type UInt16, the following code displays a compiler warning.

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
'                                ~~~

您可以通过将 Person 属性的类型从 Age 更改为 UInt16 来将 Int16 类设置为符合 CLS,即一个符合 CLS 的 16 位带符号整数。You can make the Person class CLS-compliant by changing the type of Age property from UInt16 to Int16, which is a CLS-compliant, 16-bit signed integer. 您无需更改私有 personAge 字段的类型。You do not have to change the type of the private personAge field.

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

库的公共接口包括:A library's public interface consists of the following:

  • 公共类的定义。Definitions of public classes.

  • 公共类中公共成员的定义,以及派生类可以访问的成员(即受保护的成员)的定义。Definitions of the public members of public classes, and definitions of members accessible to derived classes (that is, protected members).

  • 公共类中公共方法的参数和返回类型,以及派生类可以访问的方法的参数和返回类型。Parameters and return types of public methods of public classes, and parameters and return types of methods accessible to derived classes.

下表中将列出 CLS 遵从性规则。The rules for CLS compliance are listed in the following table. 规则的文本摘自 ECMA-335 标准:公共语言基础结构(版权所有 2012,Ecma International)。The text of the rules is taken verbatim from the ECMA-335 Standard: Common Language Infrastructure, which is Copyright 2012 by Ecma International. 有关这些规则的详细信息,请参阅以下各节。More detailed information about these rules is found in the following sections.

类别Category 查看See 规则Rule 规则编号Rule number
可访问性Accessibility 成员可访问性Member accessibility 重写继承的方法时,可访问性应不会更改(重写一个继承自其他具有可访问性 family-or-assembly 的程序集的方法除外)。Accessibility shall not be changed when overriding inherited methods, except when overriding a method inherited from a different assembly with accessibility family-or-assembly. 在此情况下,重写应具有可访问性 familyIn this case, the override shall have accessibility family. 1010
可访问性Accessibility 成员可访问性Member accessibility 类型和成员的可见性和可访问性应是这样的:只要任何成员是可见的且可访问的,则该成员签名中的类型应是可见且可访问的。The visibility and accessibility of types and members shall be such that types in the signature of any member shall be visible and accessible whenever the member itself is visible and accessible. 例如,在其程序集外部可见的公共方法不应具有其类型仅在程序集内可见的参数。For example, a public method that is visible outside its assembly shall not have an argument whose type is visible only within the assembly. 只要任何成员是可见且可访问的,则构成该成员签名中使用的实例化泛型类型的类型应是可见且可访问的。The visibility and accessibility of types composing an instantiated generic type used in the signature of any member shall be visible and accessible whenever the member itself is visible and accessible. 例如,一个在其程序集外部可见的成员签名中出现的实例化泛型类型不应具有其类型仅在程序集内可见的泛型实参。For example, an instantiated generic type present in the signature of a member that is visible outside its assembly shall not have a generic argument whose type is visible only within the assembly. 1212
数组Arrays 数组Arrays 数组应具有符合 CLS 的类型的元素,且数组的所有维度的下限应为零。Arrays shall have elements with a CLS-compliant type, and all dimensions of the array shall have lower bounds of zero. 各重载间只需区别:项是数组还是数组的元素类型。Only the fact that an item is an array and the element type of the array shall be required to distinguish between overloads. 当重载基于两个或更多数组类型时,元素类型应为命名类型。When overloading is based on two or more array types the element types shall be named types. 1616
特性Attributes 特性Attributes 特性应是 System.Attribute 类型或从该类型继承的类型。Attributes shall be of type System.Attribute, or a type inheriting from it. 4141
特性Attributes 特性Attributes CLS 只允许自定义特性编码的子集。The CLS only allows a subset of the encodings of custom attributes. 这些编码中仅应显示的类型(请参阅第 IV 部分):System.TypeSystem.StringSystem.CharSystem.BooleanSystem.ByteSystem.Int16System.Int32System.Int64System.SingleSystem.Double 以及基于符合 CLS 的基整数类型的任何枚举类型。The only types that shall appear in these encodings are (see Partition IV): System.Type, System.String, System.Char, System.Boolean, System.Byte, System.Int16, System.Int32, System.Int64, System.Single, System.Double, and any enumeration type based on a CLS-compliant base integer type. 3434
特性Attributes 特性Attributes CLS 不允许公开可见的所需修饰符(modreq,请参阅第 II 部分),但允许其不了解的可选修饰符(modopt,请参阅第 II 部分)。The CLS does not allow publicly visible required modifiers (modreq, see Partition II), but does allow optional modifiers (modopt, see Partition II) it does not understand. 3535
构造函数Constructors 构造函数Constructors 在访问继承的实例数据之前,对象构造函数应调用其基类的某个实例构造函数。An object constructor shall call some instance constructor of its base class before any access occurs to inherited instance data. (这不适用于不需要构造函数的值类型。)(This does not apply to value types, which need not have constructors.) 2121
构造函数Constructors 构造函数Constructors 对象构造函数只能在创建对象的过程中调用,并且不应将对象初始化两次。An object constructor shall not be called except as part of the creation of an object, and an object shall not be initialized twice. 2222
枚举Enumerations 枚举Enumerations 枚举的基础类型应是内置的 CLS 整数类型,字段的名称应为“value__”,并且应将该字段标记为 RTSpecialNameThe underlying type of an enum shall be a built-in CLS integer type, the name of the field shall be "value__", and that field shall be marked RTSpecialName. 77
枚举Enumerations 枚举Enumerations 有两种不同的枚举,二者由是否存在 System.FlagsAttribute(请参阅第 IV 部分库)自定义特性指示。There are two distinct kinds of enums, indicated by the presence or absence of the System.FlagsAttribute (see Partition IV Library) custom attribute. 一个表示命名的整数值;另一个表示命名的位标记(可合并以生成一个未命名的值)。One represents named integer values; the other represents named bit flags that can be combined to generate an unnamed value. enum 的值不仅限于指定的值。The value of an enum is not limited to the specified values. 88
枚举Enumerations 枚举Enumerations 枚举的文本静态字段需具有枚举本身的类型。Literal static fields of an enum shall have the type of the enum itself. 99
事件Events 事件Events 实现事件的方法将在元数据中标记为 SpecialNameThe methods that implement an event shall be marked SpecialName in the metadata. 2929
事件Events 事件Events 事件及其访问器的可访问性应相同。The accessibility of an event and of its accessors shall be identical. 3030
事件Events 事件Events 事件的 addremove 方法应同时存在或不存在。The add and remove methods for an event shall both either be present or absent. 3131
事件Events 事件Events 事件的 addremove 方法均应采用一个参数,该参数的类型定义了事件的类型,且应派生自 System.DelegateThe add and remove methods for an event shall each take one parameter whose type defines the type of the event and that shall be derived from System.Delegate. 3232
事件Events 事件Events 事件应遵循特定的命名模式。Events shall adhere to a specific naming pattern. CLS 规则 29 中引用的 SpecialName 特性应在适当的名称比较中被忽略,且应遵循标识符规则。The SpecialName attribute referred to in CLS rule 29 shall be ignored in appropriate name comparisons and shall adhere to identifier rules. 3333
异常Exceptions 异常Exceptions 引发的对象应为 System.Exception 类型或从该类型继承的类型。Objects that are thrown shall be of type System.Exception or a type inheriting from it. 但是,阻止其他类型的异常的传播无需符合 CLS 的方法。Nonetheless, CLS-compliant methods are not required to block the propagation of other types of exceptions. 4040
常规General CLS 符合性:规则CLS compliance: the Rules CLS 规则仅适用于类型中在定义程序集之外可访问或可显示的部分。CLS rules apply only to those parts of a type that are accessible or visible outside of the defining assembly. 11
常规General CLS 符合性:规则CLS compliance: the Rules 不应将不符合 CLS 的类型的成员标记为符合 CLS。Members of non-CLS compliant types shall not be marked CLS-compliant. 22
泛型Generics 泛型类型和成员Generic types and members 嵌套类型拥有的泛型形参的数目至少应与封闭类型的一样多。Nested types shall have at least as many generic parameters as the enclosing type. 嵌套类型中的泛型参数在位置上与其封闭类型中的泛型参数对应。Generic parameters in a nested type correspond by position to the generic parameters in its enclosing type. 4242
泛型Generics 泛型类型和成员Generic types and members 根据上面定义的规则,泛型类型的名称将对非嵌套类型上声明的或新引入到类型(如果嵌套)中的类型参数的数量进行编码。The name of a generic type shall encode the number of type parameters declared on the non-nested type, or newly introduced to the type if nested, according to the rules defined above. 4343
泛型Generics 泛型类型和成员Generic types and members 泛型类型应该重新声明足够多的约束,以确保对基类型或接口的任何约束能够满足泛型类型约束的需要。A generic type shall redeclare sufficient constraints to guarantee that any constraints on the base type, or interfaces would be satisfied by the generic type constraints. 44444444
泛型Generics 泛型类型和成员Generic types and members 用作对泛型参数的约束的类型本身应符合 CLS。Types used as constraints on generic parameters shall themselves be CLS-compliant. 4545
泛型Generics 泛型类型和成员Generic types and members 实例化泛型类型中的成员(包括嵌套类型)的可见性和可访问性应被认为限制在特定的实例化中,而不是限制在整个泛型类型声明中。The visibility and accessibility of members (including nested types) in an instantiated generic type shall be considered to be scoped to the specific instantiation rather than the generic type declaration as a whole. 作出以下假定:CLS 规则 12 的可见性和可访问性仍适用。Assuming this, the visibility and accessibility rules of CLS rule 12 still apply. 4646
泛型Generics 泛型类型和成员Generic types and members 对于每个抽象或虚拟泛型方法,应该有默认的具体(非抽象)实现。For each abstract or virtual generic method, there shall be a default concrete (non-abstract) implementation. 4747
接口Interfaces 接口Interfaces 符合 CLS 的接口不需要不符合 CLS 的方法的定义即可实现这些方法。CLS-compliant interfaces shall not require the definition of non-CLS compliant methods in order to implement them. 1818
接口Interfaces 接口Interfaces 符合 CLS 的接口不应定义静态方法,也不应定义字段。CLS-compliant interfaces shall not define static methods, nor shall they define fields. 1919
成员Members 类型成员概述Type members in general 全局静态字段和方法不符合 CLS。Global static fields and methods are not CLS-compliant. 3636
成员Members -- 通过使用字段初始化元数据指定文本静态值。The value of a literal static is specified through the use of field initialization metadata. 符合 CLS 的文本必须在类型与文本(或基本类型,如果文本为 enum)完全相同的字段初始化元数据中指定值。A CLS-compliant literal must have a value specified in field initialization metadata that is of exactly the same type as the literal (or of the underlying type, if that literal is an enum). 1313
成员Members 类型成员概述Type members in general vararg 约束不属于 CLS,并且 CLS 支持的唯一调用约定是标准托管调用约定。The vararg constraint is not part of the CLS, and the only calling convention supported by the CLS is the standard managed calling convention. 1515
命名约定Naming conventions 命名约定Naming conventions 针对用于管理允许启用且包含在标识符中的字符集的 Unicode Standard3.0 ,程序集应遵守其技术报告 15 的附件 7(可通过访问 https://www.unicode.org/unicode/reports/tr15/tr15-18.html 在线获得)。Assemblies shall follow Annex 7 of Technical Report 15 of the Unicode Standard3.0 governing the set of characters permitted to start and be included in identifiers, available online at https://www.unicode.org/unicode/reports/tr15/tr15-18.html. 标识符应是由 Unicode 范式 C 定义的规范格式。对于 CLS,如果两个标识符的小写映射(由不区分区域设置的 Unicode、一对一小写映射指定)相同,则它们也相同。Identifiers shall be in the canonical format defined by Unicode Normalization Form C. For CLS purposes, two identifiers are the same if their lowercase mappings (as specified by the Unicode locale-insensitive, one-to-one lowercase mappings) are the same. 也就是说,对于要在 CLS 下视为不同的两个标识符,它们应以大小写之外的差别进行区分。That is, for two identifiers to be considered different under the CLS they shall differ in more than simply their case. 但是,若要重写继承的定义,CLI 需要对使用的原始声明进行准确编码。However, in order to override an inherited definition the CLI requires the precise encoding of the original declaration be used. 44
重载Overloading 命名约定Naming conventions 在符合 CLS 的范围中引入的所有名称都应是明显独立的类型,除非名称完全相同且通过重载解析。All names introduced in a CLS-compliant scope shall be distinct independent of kind, except where the names are identical and resolved via overloading. 也就是说,CTS 允许单个类型对方法和字段使用相同的名称,但 CLS 不允许。That is, while the CTSallows a single type to use the same name for a method and a field, the CLS does not. 55
重载Overloading 命名约定Naming conventions 即使 CTS 允许区分不同的签名,但字段和嵌套类型只能由标识符比较区分。Fields and nested types shall be distinct by identifier comparison alone, even though the CTS allows distinct signatures to be distinguished. (标识符比较后)具有相同名称的方法、属性和事件应在除返回类型不同之外还具有其他差异,CLS 规则 39 中指定的差异除外。Methods, properties, and events that have the same name (by identifier comparison) shall differ by more than just the return type,except as specified in CLS Rule 39. 66
重载Overloading 重载Overloads 只可重载属性和方法。Only properties and methods can be overloaded. 3737
重载Overloading 重载Overloads 属性和方法仅可基于其参数的数目和类型进行重载,名为 op_Implicitop_Explicit 的转换运算符除外,这两种转换运算符也可基于其返回类型进行重载。Properties and methods can be overloaded based only on the number and types of their parameters, except the conversion operators named op_Implicit and op_Explicit, which can also be overloaded based on their return type. 3838
重载Overloading -- 如果类型中声明的两种或更多符合 CLS 的方法都具有相同的名称,并且对于特定的类型实例化集而言,它们具有相同的参数和返回类型,则所有这些方法在这些类型实例化时都应在语义上保持等效。If two or more CLS-compliant methods declared in a type have the same name and, for a specific set of type instantiations, they have the same parameter and return types, then all these methods shall be semantically equivalent at those type instantiations. 4848
类型Types 类型和类型成员签名Type and type member signatures System.Object 符合 CLS。System.Object is CLS-compliant. 任何其他符合 CLS 的类应从符合 CLS 的类继承。Any other CLS-compliant class shall inherit from a CLS-compliant class. 2323
属性Properties 属性Properties 实现属性的 getter 和 setter 方法的方法应在元数据中标记为 SpecialNameThe methods that implement the getter and setter methods of a property shall be marked SpecialName in the metadata. 2424
属性Properties 属性Properties 属性的访问器必须同为静态、同为虚拟或同为实例。A property’s accessors shall all be static, all be virtual, or all be instance. 2626
属性Properties 属性Properties 属性的类型应该是 getter 的返回类型和 setter 的最后一个自变量的类型。The type of a property shall be the return type of the getter and the type of the last argument of the setter. 属性参数的类型应是对应于 getter 的参数的类型和 setter 的除最后一个参数之外所有参数的类型。The types of the parameters of the property shall be the types of the parameters to the getter and the types of all but the final parameter of the setter. 所有这些类型都应该符合 CLS,并且不应是托管指针(也就是说,不应该被引用传递)。All of these types shall be CLS-compliant, and shall not be managed pointers (i.e., shall not be passed by reference). 2727
属性Properties 属性Properties 属性应遵循特定的命名模式。Properties shall adhere to a specific naming pattern. CLS 规则 24 中引用的 SpecialName 特性将在适当名称比较中被忽略,且应遵循标识符规则。The SpecialName attribute referred to in CLS rule 24 shall be ignored in appropriate name comparisons and shall adhere to identifier rules. 属性应具有 getter 和/或 setter 方法。A property shall have a getter method, a setter method, or both. 2828
类型转换Type conversion 类型转换Type conversion 如果提供 op_Implicitop_Explicit,则必须提供实现强制转换的替代方法。If either op_Implicit or op_Explicit is provided, an alternate means of providing the coercion shall be provided. 3939
类型Types 类型和类型成员签名Type and type member signatures 装箱的值类型不符合 CLS。Boxed value types are not CLS-compliant. 33
类型Types 类型和类型成员签名Type and type member signatures 显示在签名中的所有类型应符合 CLS。All types appearing in a signature shall be CLS-compliant. 构成实例化泛型类型的所有类型应符合 CLS。All types composing an instantiated generic type shall be CLS-compliant. 1111
类型Types 类型和类型成员签名Type and type member signatures 类型化的引用是不符合 CLS 的。Typed references are not CLS-compliant. 1414
类型Types 类型和类型成员签名Type and type member signatures 非托管的指针类型不符合 CLS。Unmanaged pointer types are not CLS-compliant. 1717
类型Types 类型和类型成员签名Type and type member signatures 符合 CLS 的类、值类型和接口不应要求实现不符合 CLS 的成员。CLS-compliant classes, value types, and interfaces shall not require the implementation of non-CLS-compliant members. 2020

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

System.Object 类型符合 CLS,并且是 .NET Framework 类型系统中所有对象类型的基础类型。The System.Object type is CLS-compliant and is the base type of all object types in the .NET Framework type system. .NET Framework 中的继承要么是隐式的(例如,String 类从 Object 类隐式继承),要么是显式的(例如,CultureNotFoundException 类从 ArgumentException 类显式继承,其中后者从 SystemException 类显式继承,而该类又从 Exception 类显式继承)。Inheritance in the .NET Framework is either implicit (for example, the String class implicitly inherits from the Object class) or explicit (for example, the CultureNotFoundException class explicitly inherits from the ArgumentException class, which explicitly inherits from the SystemException class, which explicitly inherits from the Exception class). 对于要符合 CLS 的派生类型,其基本类型也必须符合 CLS。For a derived type to be CLS compliant, its base type must also be CLS-compliant.

下面的示例显示基本类型不符合 CLS 的派生类型。The following example shows a derived type whose base type is not CLS-compliant. 它定义使用无符号 32 位整数作为计数器的基本 Counter 类。It defines a base Counter class that uses an unsigned 32-bit integer as a counter. 由于类通过对无符号整数进行换行来提供计数器功能,因此类标记为不符合 CLS。Because the class provides counter functionality by wrapping an unsigned integer, the class is marked as non-CLS-compliant. 因此,派生类 NonZeroCounter 也不符合 CLS。As a result, a derived class, NonZeroCounter, is also not CLS-compliant.

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。All types that appear in member signatures, including a method's return type or a property type, must be CLS-compliant. 此外,对于泛型类型:In addition, for generic types:

  • 构成实例化泛型类型的所有类型必须符合 CLS。All types that compose an instantiated generic type must be CLS-compliant.

  • 所有用作针对泛型参数的约束的类型必须符合 CLS。All types used as constraints on generic parameters must be CLS-compliant.

.NET Framework 通用类型系统包括大量直接受公共语言运行时支持且专以程序集元数据编码的内置类型。The .NET Framework common type system includes a number of built-in types that are supported directly by the common language runtime and are specially encoded in an assembly's metadata. 在这些内部类型中,下表中所列的类型都符合 CLS。Of these intrinsic types, the types listed in the following table are CLS-compliant.

符合 CLS 的类型CLS-compliant type 说明Description
Byte 8 位无符号整数8-bit unsigned integer
Int16 16 位带符号整数16-bit signed integer
Int32 32 位带符号整数32-bit signed integer
Int64 64 位带符号整数64-bit signed integer
Single 单精度浮点值Single-precision floating-point value
Double 双精度浮点值Double-precision floating-point value
Boolean truefalse 值类型true or false value type
Char UTF 16 编码单元UTF-16 encoded code unit
Decimal 非浮点十进制数字Non-floating-point decimal number
IntPtr 平台定义的大小的指针或句柄Pointer or handle of a platform-defined size
String 包含零个、一个或多个 Char 对象的集合Collection of zero, one, or more Char objects

下表中所列的内部类型不符合 CLS。The intrinsic types listed in the following table are not CLS-Compliant.

不符合类型Non-compliant type 说明Description 符合 CLS 的替代方法CLS-compliant alternative
SByte 8 位带符号整数数据类型8-bit signed integer data type Int16
TypedReference 指向对象及其运行时类型的指针Pointer to an object and its runtime type NoneNone
UInt16 16 位无符号整数16-bit unsigned integer Int32
UInt32 32 位无符号整数32-bit unsigned integer Int64
UInt64 64 位无符号整数64-bit unsigned integer Int64(可能溢出)、BigIntegerDoubleInt64 (may overflow), BigInteger, or Double
UIntPtr 未签名的指针或句柄Unsigned pointer or handle IntPtr

.NET Framework 类库或任何其他类库可能包含不符合 CLS 的其他类型;例如:The .NET Framework Class Library or any other class library may include other types that aren't CLS-compliant; for example:

  • 装箱的值类型。Boxed value types. 下面的 C# 示例创建一个具有名为 int*Value 类型的公共属性的类。The following C# example creates a class that has a public property of type int* named Value. 由于 int* 是一个装箱的值类型,因此编译器将其标记为不符合 CLS。Because an int* is a boxed value type, the compiler flags it as non-CLS-compliant.

    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
    
  • 类型化的引用是一个特殊构造,它包含一个对对象的引用和一个对类型的引用。Typed references, which are special constructs that contain a reference to an object and a reference to a type. 类型化的引用由 TypedReference 类显示在 .NET Framework 中。Typed references are represented in the .NET Framework by the TypedReference class.

如果类型不符合 CLS,则需对其应用 CLSCompliantAttribute 值为 isCompliantfalse 特性。If a type is not CLS-compliant, you should apply the CLSCompliantAttribute attribute with an isCompliant value of false to it. 有关更多信息,请参阅 CLSCompliantAttribute 特性部分。For more information, see The CLSCompliantAttribute attribute section.

下面的示例说明了方法签名和泛型类型实例化中 CLS 遵从性的问题。The following example illustrates the problem of CLS compliance in a method signature and in generic type instantiation. 它定义一个具有类型为 InvoiceItem 的属性和类型为 UInt32 的属性的 Nullable(Of UInt32) 类,以及一个具有类型为 UInt32Nullable(Of UInt32) 的参数的构造函数。It defines an InvoiceItem class with a property of type UInt32, a property of type Nullable(Of UInt32), and a constructor with parameters of type UInt32 and Nullable(Of UInt32). 您尝试编译此示例时会收到四个编译器警告。You get four compiler warnings when you try to compile this example.

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 的类型替换为符合 CLS 的类型:To eliminate the compiler warnings, replace the non-CLS-compliant types in the InvoiceItem public interface with compliant types:

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。In addition to the specific types listed, some categories of types are not CLS compliant. 其中包括非托管指针类型和函数指针类型。These include unmanaged pointer types and function pointer types. 下面的示例将生成一个编译器警告,因为它使用指向整数的指针来创建整数数组。The following example generates a compiler warning because it uses a pointer to an integer to create an array of integers.

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。For CLS-compliant abstract classes (that is, classes marked as abstract in C# or as MustInherit in Visual Basic), all members of the class must also be CLS-compliant.

命名约定Naming conventions

由于一些编程语言不区分大小写,因此标识符(例如,命名空间、类型和成员的名称)必须不仅限于大小写不同。Because some programming languages are case-insensitive, identifiers (such as the names of namespaces, types, and members) must differ by more than case. 如果两个标识符的小写映射是相同的,则这两个标识符被视为等效。Two identifiers are considered equivalent if their lowercase mappings are the same. 下面的 C# 示例定义两个公共类:PersonpersonThe following C# example defines two public classes, Person and person. 由于它们只是大小写不同,因此 C# 编译器会将其标记为不符合 CLS。Because they differ only by case, the C# compiler flags them as not CLS-compliant.

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 标准 3.0、技术报告 15 和附件 7Programming language identifiers, such as the names of namespaces, types, and members, must conform to the Unicode Standard 3.0, Technical Report 15, Annex 7. 这表示:This means that:

  • 标识符的第一个字符可以是任何 Unicode 大写字母、小写字母、标题大小写字母、修饰符字母、其他字母或字母数字。The first character of an identifier can be any Unicode uppercase letter, lowercase letter, title case letter, modifier letter, other letter, or letter number. 有关 Unicode 字符类别的信息,请参阅 System.Globalization.UnicodeCategory 枚举。For information on Unicode character categories, see the System.Globalization.UnicodeCategory enumeration.

  • 后继字符可以作为第一个字符来自任何类别,还可以包含无间隔标记、间距组合标记、十进制数字、连接器标点符号以及格式设置代码。Subsequent characters can be from any of the categories as the first character, and can also include non-spacing marks, spacing combining marks, decimal numbers, connector punctuations, and formatting codes.

在比较标识符之前,应筛选格式设置代码,并将标识符转换为 Unicode 范式 C,因为单个字符可由多个 UTF 16 编码的代码单位表示。Before you compare identifiers, you should filter out formatting codes and convert the identifiers to Unicode Normalization Form C, because a single character can be represented by multiple UTF-16-encoded code units. 在 Unicode 范式 C 中生成相同代码单位的字符序列不符合 CLS。Character sequences that produce the same code units in Unicode Normalization Form C are not CLS-compliant. 下列示例定义了一个名为 的属性(包含字符 ANGSTROM SIGN (U+212B))和另一个名为 Å 的属性(包含字符 LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5))。The following example defines a property named , which consists of the character ANGSTROM SIGN (U+212B), and a second property named Å, which consists of the character LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5). C# 和 Visual Basic 编译器都将源代码标记为不符合 CLS。Both the C# and Visual Basic compilers flag the source code as non-CLS-compliant.

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
'                       ~

特定范围内的成员名称(如程序集中的命名空间、命名空间中的类型或某类型中的成员)必须是唯一的,通过重载解析的名称除外。Member names within a particular scope (such as the namespaces within an assembly, the types within a namespace, or the members within a type) must be unique except for names that are resolved through overloading. 此要求比常规类型系统的要求更加严格,后者允许一个范围内的多个成员在作为不同类型的成员(例如,一个是方法,一个是字段时)时,可以具有相同的名称。This requirement is more stringent than that of the common type system, which allows multiple members within a scope to have identical names as long as they are different kinds of members (for example, one is a method and one is a field). 具体而言,对于类型成员:In particular, for type members:

  • 字段和嵌套类型只能通过名称区分。Fields and nested types are distinguished by name alone.

  • 具有相同名称的方法、属性和事件必须具有除返回类型不同之外的其他不同之处。Methods, properties, and events that have the same name must differ by more than just return type.

下面的示例说明了成员名称在其范围内必须是唯一的要求。The following example illustrates the requirement that member names must be unique within their scope. 它定义了一个名为 Converter 的类,该类包括四个名为 Conversion 的成员。It defines a class named Converter that includes four members named Conversion. 其中三个是方法,一个是属性。Three are methods, and one is a property. 包含 Int64 参数的方法具有唯一名称,但是,具有 Int32 参数的两个方法不是,因为返回值不被视为成员签名的一部分。The method that includes an Int64 parameter is uniquely named, but the two methods with an Int32 parameter are not, because return value is not considered a part of a member's signature. 由于属性不能具有与重载方法相同的名称,因此 Conversion 属性也违反了此要求。The Conversion property also violates this requirement, because properties cannot have the same name as overloaded methods.

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
'                                ~~~~~~~~~~

各种语言都包含唯一关键字,因此面向公共语言运行时的语言还必须提供一些机制,以便引用与关键字相符的标识符(例如,类型名称)。Individual languages include unique keywords, so languages that target the common language runtime must also provide some mechanism for referencing identifiers (such as type names) that coincide with keywords. 例如,case 是 C# 和 Visual Basic 两者中的关键字。For example, case is a keyword in both C# and Visual Basic. 但是,下面的 Visual Basic 示例可以通过使用左大括号和右大括号将名为 case 的类从 case 关键字中消除。However, the following Visual Basic example is able to disambiguate a class named case from the case keyword by using opening and closing braces. 否则,该示例将生成错误消息“关键字无法用作标识符”,并且编译将失败。Otherwise, the example would produce the error message, "Keyword is not valid as an identifier," and fail to compile.

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 符号从语言关键字中消除标识符来实例化 @ 类。The following C# example is able to instantiate the case class by using the @ symbol to disambiguate the identifier from the language keyword. 如果没有该符号,C# 编译器会显示两条错误消息:“应为类型”和“无效表达式术语‘case’”。Without it, the C# compiler would display two error messages, "Type expected" and "Invalid expression term 'case'."

using System;

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

类型转换Type conversion

公共语言规范定义了两个转换运算符:The Common Language Specification defines two conversion operators:

  • op_Implicit 用于扩大转换,不会丢失数据或精度。op_Implicit, which is used for widening conversions that do not result in loss of data or precision. 例如,Decimal 结构包含一个已重载的 op_Implicit 运算符,以将整数类型值和 Char 值转换为 Decimal 值。For example, the Decimal structure includes an overloaded op_Implicit operator to convert values of integral types and Char values to Decimal values.

  • op_Explicit 用于收缩可能导致丢失量级(将一个值转换为一个范围更小的值)或精度的转换。op_Explicit, which is used for narrowing conversions that can result in loss of magnitude (a value is converted to a value that has a smaller range) or precision. 例如,Decimal 结构包含一个已重载的 op_Explicit 运算符,以将 DoubleSingle 值转换为 Decimal,并将 Decimal 值转换为整数值:DoubleSingleCharFor example, the Decimal structure includes an overloaded op_Explicit operator to convert Double and Single values to Decimal and to convert Decimal values to integral values, Double, Single, and Char.

但是,并非所有语言都支持运算符重载或定义自定义运算符。However, not all languages support operator overloading or the definition of custom operators. 如果选择实现这些转换运算符,您还应提供另一种执行转换的方法。If you choose to implement these conversion operators, you should also provide an alternate way to perform the conversion. 我们建议提供 FromXxxToXxx 方法。We recommend that you provide FromXxx and ToXxx methods.

下面的示例定义符合 CLS 的隐式和显式转换。The following example defines CLS-compliant implicit and explicit conversions. 它创建表示带符号的双精度浮点数字的 UDouble 类。It creates a UDouble class that represents an signed double-precision, floating-point number. 它提供从 UDoubleDouble 的隐式转换和从 UDoubleSingle、从 DoubleUDouble 以及从 SingleUDouble 的显式转换。It provides for implicit conversions from UDouble to Double and for explicit conversions from UDouble to Single, Double to UDouble, and Single to UDouble. 它还将 ToDouble 方法定义为隐式转换运算符的替代方法,并将 ToSingleFromDoubleFromSingle 方法定义为显式转换运算符的替代方法。It also defines a ToDouble method as an alternative to the implicit conversion operator and the ToSingle, FromDouble, and FromSingle methods as alternatives to the explicit conversion operators.

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

数组Arrays

符合 CLS 的数组符合以下规则:CLS-compliant arrays conform to the following rules:

  • 数组的所有维度必须具有零下限。All dimensions of an array must have a lower bound of zero. 下面的示例创建一个不符合 CLS 的数组,其下限为 1。The following example creates a non-CLS-compliant array with a lower bound of one. 请注意,无论是否存在 CLSCompliantAttribute 特性,编译器都不检测由 Numbers.GetTenPrimes 方法返回的数组是否符合 CLS。Note that, despite the presence of the CLSCompliantAttribute attribute, the compiler does not detect that the array returned by the Numbers.GetTenPrimes method is not CLS-compliant.

    [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 的类型。All array elements must consist of CLS-compliant types. 下面的示例定义返回不符合 CLS 的数组的两种方法。The following example defines two methods that return non-CLS-compliant arrays. 第一个返回 UInt32 值的数组。The first returns an array of UInt32 values. 第二个返回包括 ObjectInt32 值的 UInt32 数组。The second returns an Object array that includes Int32 and UInt32 values. 虽然编译器因第一个数组是 UInt32 类型而将其标识为不合规,但无法识别第二个包含不符合 CLS 元素的数组。Although the compiler identifies the first array as non-compliant because of its UInt32 type, it fails to recognize that the second array includes non-CLS-compliant elements.

    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()
    '                              ~~~~~~~~~~~~
    
  • 具有数组参数的方法的重载决策基于它们是否为数组及它们的元素类型。Overload resolution for methods that have array parameters is based on the fact that they are arrays and on their element type. 因此,以下对重载的 GetSquares 方法的定义符合 CLS。For this reason, the following definition of an overloaded GetSquares method is CLS-compliant.

    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
    

接口Interfaces

符合 CLS 的接口可以定义属性、事件和虚拟方法(没有实现的方法)。CLS-compliant interfaces can define properties, events, and virtual methods (methods with no implementation). 符合 CLS 的接口不能有:A CLS-compliant interface cannot have any of the following:

  • 静态方法或静态字段。Static methods or static fields. 如果您在接口中定义静态成员,那么 C# 和 Visual Basic 编译器将生成编译器错误。Both the C# and Visual Basic compilers generate compiler errors if you define a static member in an interface.

  • 字段。Fields. 如果您在接口中定义字段,则 C# 和 Visual Basic 编译器将生成编译器错误。Both the C# and Visual Basic compilers generate compiler errors if you define a field in an interface.

  • 不符合 CLS 的方法。Methods that are not CLS-compliant. 例如,下面的接口定义包括方法 INumber.GetUnsigned,该方法标记为不符合 CLS。For example, the following interface definition includes a method, INumber.GetUnsigned, that is marked as non-CLS-compliant. 此示例生成编译器警告。This example generates a compiler warning.

    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 的成员。Because of this rule, CLS-compliant types are not required to implement non-CLS-compliant members. 如果一个符合 CLS 的框架公开实现不符合 CLS 接口的类,则其还应提供所有不符合 CLS 的成员的具体实现。If a CLS-compliant framework does expose a class that implements a non-CLS compliant interface, it should also provide concrete implementations of all non-CLS-compliant members.

符合 CLS 的语言编译器还必须允许类提供在多个接口中具有相同名称和签名的成员的单独实现。CLS-compliant language compilers must also allow a class to provide separate implementations of members that have the same name and signature in multiple interfaces. C# 和 Visual Basic 都支持显式接口实现以提供同名方法的不同实现。Both C# and Visual Basic support explicit interface implementations to provide different implementations of identically named methods. Visual Basic 还支持 Implements 关键字,可让您显式指定特定成员要实现的接口和成员。Visual Basic also supports the Implements keyword, which enables you to explicitly designate which interface and member a particular member implements. 下面的示例通过定义一个将 TemperatureICelsius 接口作为显式接口实现的 IFahrenheit 类来说明此方案。The following example illustrates this scenario by defining a Temperature class that implements the ICelsius and IFahrenheit interfaces as explicit interface implementations.

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: {0} degrees", 
                        cTemp.GetTemperature());
      Console.WriteLine("Temperature in Fahrenheit: {0} degrees", 
                        fTemp.GetTemperature());
   }
}
// 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

枚举Enumerations

符合 CLS 的枚举必须遵循下列规则:CLS-compliant enumerations must follow these rules:

  • 枚举的基础类型必须是符合 CLS 的内部整数(ByteInt16Int32Int64)。The underlying type of the enumeration must be an intrinsic CLS-compliant integer (Byte, Int16, Int32, or Int64). 例如,下面的代码尝试定义基础类型为 UInt32 的枚举并生成编译器警告。For example, the following code tries to define an enumeration whose underlying type is UInt32 and generates a compiler warning.

    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 特性的单个实例字段。An enumeration type must have a single instance field named Value__ that is marked with the FieldAttributes.RTSpecialName attribute. 这使得您可以隐式引用字段值。This enables you to reference the field value implicitly.

  • 枚举包括文本静态字段,该字段的类型与枚举本身的类型匹配。An enumeration includes literal static fields whose types match the type of the enumeration itself. 例如,如果您用 StateState.On 的值定义 State.Off 枚举,则 State.OnState.Off 都是文本静态字段,其类型为 StateFor example, if you define a State enumeration with values of State.On and State.Off, State.On and State.Off are both literal static fields whose type is State.

  • 有两种枚举:There are two kinds of enumerations:

    • 一种表示一组互斥的命名整数值的枚举。An enumeration that represents a set of mutually exclusive, named integer values. 这种类型的枚举由缺少 System.FlagsAttribute 自定义特性表示。This type of enumeration is indicated by the absence of the System.FlagsAttribute custom attribute.

    • 一种表示可结合用来生成未命名值的一组位标志的枚举。An enumeration that represents a set of bit flags that can combine to generate an unnamed value. 这种类型的枚举由存在 System.FlagsAttribute 自定义特性表示。This type of enumeration is indicated by the presence of the System.FlagsAttribute custom attribute.

    有关详细信息,请参阅 Enum 结构的文档。For more information, see the documentation for the Enum structure.

  • 枚举的值不限于其指定值的范围。The value of an enumeration is not limited to the range of its specified values. 换言之,枚举中的值的范围是其基础值的范围。In other words, the range of values in an enumeration is the range of its underlying value. 您可以使用 Enum.IsDefined 方法来确定指定的值是否为枚举成员。You can use the Enum.IsDefined method to determine whether a specified value is a member of an enumeration.

类型成员概述Type members in general

公共语言规范要求将所有字段和方法作为特定类的成员进行访问。The Common Language Specification requires all fields and methods to be accessed as members of a particular class. 因此,全局静态字段和方法(即,除类型外定义的静态字段或方法)不符合 CLS。Therefore, global static fields and methods (that is, static fields or methods that are defined apart from a type) are not CLS-compliant. 如果您尝试在您的源代码中包括全局字段或方法,则 C# 和 Visual Basic 编译器都会生成编译器错误。If you try to include a global field or method in your source code, both the C# and Visual Basic compilers generate a compiler error.

公共语言规范仅支持标准托管调用约定。The Common Language Specification supports only the standard managed calling convention. 它不支持非托管调用约定和带使用 varargs 关键字标记的变量参数列表的方法。It doesn't support unmanaged calling conventions and methods with variable argument lists marked with the varargs keyword. 对于符合标准托管调用约定的变量自变量列表,请使用 ParamArrayAttribute 特性或单个语言的实现,如 C# 中的 params 关键字和 Visual Basic 中的 ParamArray 关键字。For variable argument lists that are compatible with the standard managed calling convention, use the ParamArrayAttribute attribute or the individual language's implementation, such as the params keyword in C# and the ParamArray keyword in Visual Basic.

成员可访问性Member accessibility

重写继承成员不能更改该成员的可访问性。Overriding an inherited member cannot change the accessibility of that member. 例如,无法在派生类中通过私有方法重写基类中的公共方法。For example, a public method in a base class cannot be overridden by a private method in a derived class. 有一个例外:由其他程序集中的类型重写的程序集中的 protected internal(在 C# 中)或 Protected Friend(在 Visual Basic 中)成员。There is one exception: a protected internal (in C#) or Protected Friend (in Visual Basic) member in one assembly that is overridden by a type in a different assembly. 在该示例中,重写的可访问性是 ProtectedIn that case, the accessibility of the override is Protected.

下面的示例说明了当 CLSCompliantAttribute 特性设置为 true,并且 Human(它是派生自 Animal 的类)尝试将 Species 属性的可访问性从公开更改为私有时生成的错误。The following example illustrates the error that is generated when the CLSCompliantAttribute attribute is set to true, and Human, which is a class derived from Animal, tries to change the accessibility of the Species property from public to private. 如果该示例的可访问性更改为公共,则其编译成功。The example compiles successfully if its accessibility is changed to public.

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

如果某个成员是可访问的,则该成员签名中的类型必须是可访问的。Types in the signature of a member must be accessible whenever that member is accessible. 例如,这意味着公共成员不能包含类型是私有的、受保护的或内部的参数。For example, this means that a public member cannot include a parameter whose type is private, protected, or internal. 下面的示例说明了当 StringWrapper 类构造函数公开一个用于确定如何包装字符串值的内部 StringOperationType 枚举值时出现的编译器错误。The following example illustrates the compiler error that results when a StringWrapper class constructor exposes an internal StringOperationType enumeration value that determines how a string value should be wrapped.

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)
'                              ~~~~~~~~~~~~~~~~~~~

泛型类型和成员Generic types and members

嵌套类型拥有的泛型参数数目总是至少与封闭类型的一样多。Nested types always have at least as many generic parameters as their enclosing type. 它们按位置对应于封闭类型中的泛型参数。These correspond by position to the generic parameters in the enclosing type. 泛型类型还可以包括新的泛型参数。The generic type can also include new generic parameters.

一个包含类型及其嵌套类型的泛型类型参数之间的关系可能由各种语言的语法隐藏。The relationship between the generic type parameters of a containing type and its nested types may be hidden by the syntax of individual languages. 在下面的示例中,泛型类型 Outer<T> 包含两个嵌套的类:Inner1AInner1B<U>In the following example, a generic type Outer<T> contains two nested classes, Inner1A and Inner1B<U>. 对于每个类从 ToString 继承的 Object.ToString 方法的调用,表示每个嵌套的类包括其包含的类的类型参数。The calls to the ToString method, which each class inherits from Object.ToString, show that each nested class includes the type parameters of its containing class.

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]

泛型类型名称采用 name`n 格式进行编码,其中 name 是类型名称,` 是字符文本,而 n 是针对类型声明的参数数目,或对于嵌套泛型类型为最近引入的类型参数的数目 。Generic type names are encoded in the form name`n, where name is the type name, ` is a character literal, and n is the number of parameters declared on the type, or, for nested generic types, the number of newly introduced type parameters. 此泛型类型名称的编码主要对使用反射来访问库中符合 CLS 的泛型类型的开发人员很有用。This encoding of generic type names is primarily of interest to developers who use reflection to access CLS-complaint generic types in a library.

如果将约束应用于泛型类型,则任何用作约束的类型也必须符合 CLS。If constraints are applied to a generic type, any types used as constraints must also be CLS-compliant. 下面的示例定义一个名为 BaseClass 的不符合 CLS 的类和一个其类型参数必须派生自 BaseCollection 的名为 BaseClass 的泛型类。The following example defines a class named BaseClass that is not CLS-compliant and a generic class named BaseCollection whose type parameter must derive from BaseClass. 但由于 BaseClass 不符合 CLS,因此编译器会发出警告。But because BaseClass is not CLS-compliant, the compiler emits a warning.

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)
'                                        ~~~~~~~~~

如果泛型类型派生自泛型基本类型,则其必须重新声明所有约束,以确保也满足对基本类型的约束。If a generic type is derived from a generic base type, it must redeclare any constraints so that it can guarantee that constraints on the base type are also satisfied. 下面的示例定义可表示任何数值类型的 Number<T>The following example defines a Number<T> that can represent any numeric type. 它还定义表示浮点值的 FloatingPoint<T> 类。It also defines a FloatingPoint<T> class that represents a floating point value. 但是,源代码无法编译,因为它未将 Number<T> 上(T 必须是值类型)的约束应用于 FloatingPoint<T>However, the source code fails to compile, because it does not apply the constraint on Number<T> (that T must be a value type) to 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 comple 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 comple 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> 类中,则该示例成功编译。The example compiles successfully if the constraint is added to the FloatingPoint<T> class.

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           

公共语言规范对嵌套类型和受保护成员规定了一个保守的按实例化模型。The Common Language Specification imposes a conservative per-instantiation model for nested types and protected members. 开放式泛型类型不能公开具有包含嵌套的、受保护的泛型类型的特定实例化的签名的字段或成员。Open generic types cannot expose fields or members with signatures that contain a specific instantiation of a nested, protected generic type. 扩大泛型基类或接口的特定实例化的非泛型类型不能公开带签名的字段或成员,此类字段或成员包含嵌套的、受保护的泛型类型的不同实例化。Non-generic types that extend a specific instantiation of a generic base class or interface cannot expose fields or members with signatures that contain a different instantiation of a nested, protected generic type.

下面的示例定义一个泛型类型 C1<T>(或 Visual Basic 中的 C1(Of T))和一个受保护的类 C1<T>.N(或 Visual Basic 中的 C1(Of T).N)。The following example defines a generic type, C1<T> (or C1(Of T) in Visual Basic), and a protected class, C1<T>.N (or C1(Of T).N in Visual Basic). C1<T> 有两个方法:M1M2C1<T> has two methods, M1 and M2. 但是,M1 并不符合 CLS,因为它试图从 C1<T>(或 C1(Of T))返回一个 C1<int>.N(或 C1(Of Integer).N)对象。However, M1 is not CLS-compliant because it tries to return a C1<int>.N (or C1(Of Integer).N) object from C1<T> (or C1(Of T)). 另一个名为 C2 的类派生自 C1<long>(或 C1(Of Long))。A second class, C2, is derived from C1<long> (or C1(Of Long)). 它有两个方法:M3M4It has two methods, M3 and M4. M3 不符合 CLS,因为它尝试从 C1<int>.N 的子类中返回 C1(Of Integer).N(或 C1<long>)对象。M3 is not CLS-compliant because it tries to return a C1<int>.N (or C1(Of Integer).N) object from a subclass of C1<long>. 请注意,语言编译器可具有更高限制。Note that language compilers can be even more restrictive. 在此示例中,Visual Basic 在尝试编译 M4 时显示错误。In this example, Visual Basic displays an error when it tries to compile 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)  
'                             ~~~~~~~~~~~~~

构造函数Constructors

符合 CLS 的类和结构中的构造函数必须遵循下列规则:Constructors in CLS-compliant classes and structures must follow these rules:

  • 派生类的构造函数必须先调用其基类的实例构造函数,然后才能访问继承的实例数据。A constructor of a derived class must call the instance constructor of its base class before it accesses inherited instance data. 存在此要求是因为基类构造函数并不由它们的派生类继承。This requirement is due to the fact that base class constructors are not inherited by their derived classes. 此规则不适用于不支持直接继承的结构。This rule does not apply to structures, which do not support direct inheritance.

    通常,编译器独立地强制实施 CLS 遵从性的此规则,如下面的示例所示。Typically, compilers enforce this rule independently of CLS compliance, as the following example shows. 它创建派生自 Doctor 类的 Person 类,但 Doctor 类无法调用 Person 类构造函数来初始化继承的实例字段。It creates a Doctor class that is derived from a Person class, but the Doctor class fails to call the Person class constructor to initialize inherited instance fields.

    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()
    '                  ~~~
    
  • 只有在创建对象时才能调用对象构造函数。An object constructor cannot be called except to create an object. 此外,不能将一个对象初始化两次。In addition, an object cannot be initialized twice. 例如,这意味着 Object.MemberwiseClone 和反序列化方法(如 BinaryFormatter.Deserialize)不得调用构造函数。For example, this means that Object.MemberwiseClone and deserialization methods such as BinaryFormatter.Deserialize must not call constructors.

属性Properties

符合 CLS 的类型的属性必须遵循下列规则:Properties in CLS-compliant types must follow these rules:

  • 属性必须具有 setter 和/或 getter。A property must have a setter, a getter, or both. 在程序集中,这些作为特殊方法实现,这意味着它们将显示为单独的方法(getter 命名为 get_propertyname,setter 命名为 set_propertyname),且在程序集元数据中标记为 SpecialNameIn an assembly, these are implemented as special methods, which means that they will appear as separate methods (the getter is named get_propertyname and the setter is set_propertyname) marked as SpecialName in the assembly's metadata. C# 和 Visual Basic 编译器会自动执行此规则,而无需应用 CLSCompliantAttribute 特性。The C# and Visual Basic compilers enforce this rule automatically without the need to apply the CLSCompliantAttribute attribute.

  • 属性的类型是属性 getter 的返回类型和 setter 的最后一个自变量。A property's type is the return type of the property getter and the last argument of the setter. 这些类型必须符合 CLS,并且不能通过引用将参数分配到属性中(即它们不能为托管指针)。These types must be CLS compliant, and arguments cannot be assigned to the property by reference (that is, they cannot be managed pointers).

  • 如果属性包含 getter 和 setter 两者,则它们必须都是虚拟的、静态的或实例。If a property has both a getter and a setter, they must both be virtual, both static, or both instance. C# 和 Visual Basic 编译器通过它们的属性定义语法自动执行此规则。The C# and Visual Basic compilers automatically enforce this rule through their property definition syntax.

事件Events

事件由其名称和类型定义。An event is defined by its name and its type. 事件类型是用于指示事件的委托。The event type is a delegate that is used to indicate the event. 例如,AppDomain.AssemblyResolve 事件的类型为 ResolveEventHandlerFor example, the AppDomain.AssemblyResolve event is of type ResolveEventHandler. 除事件本身外,带有基于事件名称的名称的三种方法提供事件的实现并在程序集的元数据中标记为 SpecialNameIn addition to the event itself, three methods with names based on the event name provide the event's implementation and are marked as SpecialName in the assembly's metadata:

  • 用于添加事件处理程序的名为 add_EventName 的方法。A method for adding an event handler, named add_EventName. 例如,AppDomain.AssemblyResolve 事件的事件订阅方法名为 add_AssemblyResolveFor example, the event subscription method for the AppDomain.AssemblyResolve event is named add_AssemblyResolve.

  • 用于移除事件处理程序的名为 remove_EventName 的方法。A method for removing an event handler, named remove_EventName. 例如,AppDomain.AssemblyResolve 事件的移除方法名为 remove_AssemblyResolveFor example, the removal method for the AppDomain.AssemblyResolve event is named remove_AssemblyResolve.

  • 用于指示事件已发生的名为 raise_EventName 的方法。A method for indicating that the event has occurred, named raise_EventName.

备注

大多数关于事件的公共语言规范的规则都通过语言编译器实施,且对组件开发人员是透明的。Most of the Common Language Specification's rules regarding events are implemented by language compilers and are transparent to component developers.

用于添加、移除和引发事件的方法必须拥有相同的可访问性。The methods for adding, removing, and raising the event must have the same accessibility. 它们还必须都为静态、实例或虚拟的。They must also all be static, instance, or virtual. 用于添加和移除事件的方法具有一个类型为事件委托类型的参数。The methods for adding and removing an event have one parameter whose type is the event delegate type. 添加和移除方法必须同时存在或同时不存在。The add and remove methods must both be present or both be absent.

如果两个读取之间的温度更改等于或超过阈值,则下面的示例将定义一个名为 Temperature 的类,该类会引发 TemperatureChanged 事件且符合 CLS。The following example defines a CLS-compliant class named Temperature that raises a TemperatureChanged event if the change in temperature between two readings equals or exceeds a threshold value. Temperature 类显式定义 raise_TemperatureChanged 方法,以便其可以有选择地执行事件处理程序。The Temperature class explicitly defines a raise_TemperatureChanged method so that it can selectively execute event handlers.

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 {0} to {1}", e.OldTemperature, e.CurrentTemperature);   
   }
   
   public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)
   { 
      Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature, 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

OverloadsOverloads

公共语言规范对重载成员有下列要求:The Common Language Specification imposes the following requirements on overloaded members:

  • 成员可以根据参数数量和任何参数的类型进行重载。Members can be overloaded based on the number of parameters and the type of any parameter. 在重载间进行区分时,不考虑应用于方法或其参数的调用约定、返回类型、自定义修饰符,也不考虑是按照值还是引用传递参数。Calling convention, return type, custom modifiers applied to the method or its parameter, and whether parameters are passed by value or by reference are not considered when differentiating between overloads. 有关示例,请参阅命名约定部分中名称在范围内必须是唯一的代码需求。For an example, see the code for the requirement that names must be unique within a scope in the Naming conventions section.

  • 只可重载属性和方法。Only properties and methods can be overloaded. 无法重载字段和事件。Fields and events cannot be overloaded.

  • 泛型方法可以基于其泛型参数的数目进行重载。Generic methods can be overloaded based on the number of their generic parameters.

备注

op_Explicitop_Implicit 运算符是返回值不被视为重载决策的方法签名的一部分的规则的例外情况。The op_Explicit and op_Implicit operators are exceptions to the rule that return value is not considered part of a method signature for overload resolution. 可以基于这两个运算符的参数和返回值对其进行重载。These two operators can be overloaded based on both their parameters and their return value.

异常Exceptions

异常对象必须从 System.Exception 派生或从派生自 System.Exception 的另一种类型派生。Exception objects must derive from System.Exception or from another type derived from System.Exception. 下面的示例说明了当名为 ErrorClass 的自定义类用于异常处理时产生的编译器错误。The following example illustrates the compiler error that results when a custom class named ErrorClass is used for exception handling.

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.ExceptionTo correct this error, the ErrorClass class must inherit from System.Exception. 此外,必须重写 Message 属性。In addition, the Message property must be overridden. 下面的示例更正这些错误以定义符合 CLS 的 ErrorClass 类。The following example corrects these errors to define an ErrorClass class that is CLS-compliant.

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

特性Attributes

在 .NET Framework 程序集中,自定义特性提供了一个可扩展机制,用于存储自定义特性和检索有关编程对象(如程序集、类型、成员和方法参数)的元数据。In.NET Framework assemblies, custom attributes provide an extensible mechanism for storing custom attributes and retrieving metadata about programming objects, such as assemblies, types, members, and method parameters. 自定义特性必须从 System.Attribute 派生或从派生自 System.Attribute 的类型派生。Custom attributes must derive from System.Attribute or from a type derived from System.Attribute.

下面的示例与此规则冲突。The following example violates this rule. 它定义了不是从 NumericAttribute 派生的 System.Attribute 类。It defines a NumericAttribute class that does not derive from System.Attribute. 请注意,编译器错误仅当应用不符合 CLS 的特性时会出现,而在定义类时不会出现。Note that a compiler error results only when the non-CLS-compliant attribute is applied, not when the class is defined.

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 的特性的属性只能公开以下类型:The constructor or the properties of a CLS-compliant attribute can expose only the following types:

下面的示例定义了从 DescriptionAttribute 派生的 Attribute 类。The following example defines a DescriptionAttribute class that derives from Attribute. 类构造函数具有 Descriptor 类型的参数,因此,该类不符合 CLS。The class constructor has a parameter of type Descriptor, so the class is not CLS-compliant. 请注意,C# 编译器发出警告,但编译成功,而 Visual Basic 编译器既不发出警告也不报告错误。Note that the C# compiler emits a warning but compiles successfully, whereas the Visual Basic compiler emits neither a warning nor an error.

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 特性The CLSCompliantAttribute attribute

CLSCompliantAttribute 特性用于指示程序元素是否使用公共语言规范进行编译。The CLSCompliantAttribute attribute is used to indicate whether a program element complies with the Common Language Specification. CLSCompliantAttribute.CLSCompliantAttribute(Boolean) 构造函数包含一个所需参数(isCompliant),此参数指示该程序元素是否符合 CLS。The CLSCompliantAttribute.CLSCompliantAttribute(Boolean) constructor includes a single required parameter, isCompliant, that indicates whether the program element is CLS-compliant.

编译时,编译器检测到假定为符合 CLS 的不合规元素,并发出警告。At compile time, the compiler detects non-compliant elements that are presumed to be CLS-compliant and emits a warning. 编译器不会对显式声明为不符合标准的类型或成员发出警告。The compiler does not emit warnings for types or members that are explicitly declared to be non-compliant.

组件开发人员可以通过两种方法使用 CLSCompliantAttribute 特性:Component developers can use the CLSCompliantAttribute attribute in two ways:

  • 定义由组件公开的公共接口的符合 CLS 的部件以及不符合 CLS 的部件。To define the parts of the public interface exposed by a component that are CLS-compliant and the parts that are not CLS-compliant. 如果特性用于将特定程序元素标记为符合 CLS,则使用该特性可保证能通过面向 .NET Framework 的所有语言和工具访问这些元素。When the attribute is used to mark particular program elements as CLS-compliant, its use guarantees that those elements are accessible from all languages and tools that target the .NET Framework.

  • 确保组件库的公共接口仅公开符合 CLS 的程序元素。To ensure that the component library's public interface exposes only program elements that are CLS-compliant. 如果元素不符合 CLS,则编译器通常会发出一个警告。If elements are not CLS-compliant, compilers will generally issue a warning.

警告

在某些情况下,语言编译器执行符合 CLS 的规则,而不管是否使用 CLSCompliantAttribute 特性。In some cases, language compilers enforce CLS-compliant rules regardless of whether the CLSCompliantAttribute attribute is used. 例如,在接口中定义静态成员会违反 CLS 规则。For example, defining a static member in an interface violates a CLS rule. 就这一点而言,如果在接口中定义 static(在 C# 中)或 Shared(在 Visual Basic 中)成员,C# 和 Visual Basic 编译器都会显示错误消息,且无法编译应用。In this regard, if you define a static (in C#) or Shared (in Visual Basic) member in an interface, both the C# and Visual Basic compilers display an error message and fail to compile the app.

CLSCompliantAttribute 特性标记为具有 AttributeUsageAttribute 值的 AttributeTargets.All 特性。The CLSCompliantAttribute attribute is marked with an AttributeUsageAttribute attribute that has a value of AttributeTargets.All. 利用此值,您可以将 CLSCompliantAttribute 特性应用于任何程序元素,包括程序集、模块、类型(类、结构、枚举、接口和委托)、类型成员(构造函数、方法、属性、字段和事件)、参数、泛型参数和返回值。This value allows you to apply the CLSCompliantAttribute attribute to any program element, including assemblies, modules, types (classes, structures, enumerations, interfaces, and delegates), type members (constructors, methods, properties, fields, and events), parameters, generic parameters, and return values. 但实际上,您只应将该特性应用于程序集、类型和类型成员。However, in practice, you should apply the attribute only to assemblies, types, and type members. 否则,编译器在库的公共接口中遇到不符合标准的参数、泛型参数或返回值时,将忽略此特性并继续生成编译器警告。Otherwise, compilers ignore the attribute and continue to generate compiler warnings whenever they encounter a non-compliant parameter, generic parameter, or return value in your library's public interface.

CLSCompliantAttribute 特性的值由包含的程序元素继承。The value of the CLSCompliantAttribute attribute is inherited by contained program elements. 例如,如果程序集标记为符合 CLS,则其类型也符合 CLS。For example, if an assembly is marked as CLS-compliant, its types are also CLS-compliant. 如果类型标记为符合 CLS,则其嵌套的类型和成员也符合 CLS。If a type is marked as CLS-compliant, its nested types and members are also CLS-compliant.

您可以通过将 CLSCompliantAttribute 特性应用到包含的编程元素来显式重写继承的遵从性。You can explicitly override the inherited compliance by applying the CLSCompliantAttribute attribute to a contained program element. 例如,可以使用具有 CLSCompliantAttribute 值为 isCompliantfalse 特性来定义符合标准的程序集中的不符合标准的类型,还可以使用 isCompliant 值为 true 的特性来定义不符合标准的程序集中的符合标准的类型。For example, you can use the CLSCompliantAttribute attribute with an isCompliant value of false to define a non-compliant type in a compliant assembly, and you can use the attribute with an isCompliant value of true to define a compliant type in a non-compliant assembly. 您还可以在符合标准的类型中定义不符合标准的成员。You can also define non-compliant members in a compliant type. 但是,不符合标准的类型无法拥有符合标准的成员,因此您无法使用 isCompliant 值为 true 的特性从一个不符合标准的类型重写继承。However, a non-compliant type cannot have compliant members, so you cannot use the attribute with an isCompliant value of true to override inheritance from a non-compliant type.

在开发组件时,应始终使用 CLSCompliantAttribute 特性来指示您的程序集、其类型及其成员是否符合 CLS。When you are developing components, you should always use the CLSCompliantAttribute attribute to indicate whether your assembly, its types, and its members are CLS-compliant.

创建符合 CLS 的组件:To create CLS-compliant components:

  1. 使用 CLSCompliantAttribute 将程序集标记为符合 CLS。Use the CLSCompliantAttribute to mark you assembly as CLS-compliant.

  2. 将程序集中不符合 CLS 的所有公开的类型标记为不符合标准。Mark any publicly exposed types in the assembly that are not CLS-compliant as non-compliant.

  3. 将符合 CLS 的类型中的所有公开的成员标记为不符合标准。Mark any publicly exposed members in CLS-compliant types as non-compliant.

  4. 为不符合 CLS 的成员提供符合 CLS 的替代项。Provide a CLS-compliant alternative for non-CLS-compliant members.

如果已成功标记所有不符合标准的类型和成员,您的编译器不会发出任何不符合警告。If you've successfully marked all your non-compliant types and members, your compiler should not emit any non-compliance warnings. 但是,您应指出哪些成员不符合 CLS 并在产品文档中列出其不符合 CLS 的替代项。However, you should indicate which members are not CLS-compliant and list their CLS-compliant alternatives in your product documentation.

下面的示例使用 CLSCompliantAttribute 特性定义符合 CLS 的程序集和类型 CharacterUtilities,该类型具有两个不符合 CLS 的成员。The following example uses the CLSCompliantAttribute attribute to define a CLS-compliant assembly and a type, CharacterUtilities, that has two non-CLS-compliant members. 由于这两个成员标记有 CLSCompliant(false) 特性,因此编译器不生成任何警告。Because both members are tagged with the CLSCompliant(false) attribute, the compiler produces no warnings. 该类还为两种方法提供符合 CLS 的替代项。The class also provides a CLS-compliant alternative for both methods. 通常,我们只向 ToUTF16 方法添加两个重载,以便提供符合 CLS 的替代项。Ordinarily, we would just add two overloads to the ToUTF16 method to provide CLS-compliant alternatives. 但是,由于无法基于返回值重载这些方法,因此符合 CLS 的方法的名称不同于不符合标准的方法的名称。However, because methods cannot be overloaded based on return value, the names of the CLS-compliant methods are different from the names of the non-compliant methods.

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 遵从性才会引起关注。If you are developing an app rather than a library (that is, if you aren't exposing types or members that can be consumed by other app developers), the CLS compliance of the program elements that your app consumes are of interest only if your language does not support them. 在这种情况下,当您尝试使用不符合 CLS 的元素时,您的语言编译器将生成错误。In that case, your language compiler will generate an error when you try to use a non-CLS-compliant element.

跨语言互操作性Cross-Language Interoperability

语言独立性可能有许多含义。Language independence has a number of possible meanings. 语言独立性和与语言无关的组件一文讨论了其中一个含义,其涉及将用一种语言编写的类型无缝地用于用另一种语言编写的应用程序。One meaning, which is discussed in the article Language Independence and Language-Independent Components, involves seamlessly consuming types written in one language from an app written in another language. 本文介绍第二个含义,涉及将用多种语言编写的代码组合到一个 .NET Framework 程序集。A second meaning, which is the focus of this article, involves combining code written in multiple languages into a single .NET Framework assembly.

以下示例通过创建一个名为 Utilities.dll 的包含两个类(NumericLibStringLib)的类库演示了跨语言互操作性。The following example illustrates cross-language interoperability by creating a class library named Utilities.dll that includes two classes, NumericLib and StringLib. NumericLib 类用 C# 编写类,StringLib 类用 Visual Basic 编写。The NumericLib class is written in C#, and the StringLib class is written in Visual Basic. 以下是 StringUtil.vb 的源代码,该源代码在其 ToTitleCase 类中包含一个成员StringLibHere's the source code for StringUtil.vb, which includes a single member, ToTitleCase, in its StringLib class.

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 的源代码,定义具有 NumericLibIsEven 两个成员的 NearZero 类。Here's the source code for NumberUtil.cs, which defines a NumericLib class that has two members, IsEven and NearZero.

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; 
   }
}

若要将两个类打包到单个程序集中,必须将它们编译到模块中。To package the two classes in a single assembly, you must compile them into modules. 要将 Visual Basic 源代码文件编译到模块,请使用此命令:To compile the Visual Basic source code file into a module, use this command:

vbc /t:module StringUtil.vb

有关 Visual Basic 编译器的命令行语法的详细信息,请参阅从命令行生成For more information about the command-line syntax of the Visual Basic compiler, see Building from the Command Line.

要将 C# 源代码文件编译到模块,请使用此命令:To compile the C# source code file into a module, use this command:

csc /t:module NumberUtil.cs

有关 C# 编译器的命令行语法的详细信息,请参阅在命令行上使用 csc.exe 生成For more information about the command-line syntax of the C# compiler, see Command-line Building With csc.exe.

然后,可以使用链接器选项将两个模块编译到一个程序集:You then use the Linker options to compile the two modules into an assembly:

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

然后以下实例调用 NumericLib.NearZeroStringLib.ToTitleCase 方法。The following example then calls the NumericLib.NearZero and StringLib.ToTitleCase methods. 请注意 Visual Basic 代码和 C# 代码都能够访问这两个类中的方法。Note that both the Visual Basic code and the C# code are able to access the methods in both classes.

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 代码,请使用此命令:To compile the Visual Basic code, use this command:

vbc example.vb /r:UtilityLib.dll

要使用 C# 进行编译,请将编译器的名称从 vbc 更改为 csc,将文件扩展名从 .vb 更改为 .cs:To compile with C#, change the name of the compiler from vbc to csc, and change the file extension from .vb to .cs:

csc example.cs /r:UtilityLib.dll

请参阅See also