.NET 與語言無關。 這表示身為開發人員,您可以使用以 .NET 實作為目標的其中一種語言進行開發,例如 C#、F# 和 Visual Basic。 您可以存取針對 .NET 實作開發之類別庫的類型和成員,而不需要知道原本撰寫的語言,也不需要遵循任何原始語言的慣例。 如果您是元件開發人員,不論其語言為何,任何 .NET 應用程式都可以存取您的元件。
備註
本文的第一個部分討論如何建立與語言無關的元件,也就是以任何語言撰寫的應用程式可以使用的元件。 您也可以從以多種語言撰寫的原始程式碼建立單一元件或應用程式;請參閱本文第二部分中 跨語言互作性。
若要與以任何語言撰寫的其他物件完全互動,對象必須只向呼叫者公開所有語言通用的功能。 這個常見的一組功能是由 Common Language Specification (CLS) 所定義,這是套用至產生元件的一組規則。 ECMA-335 標準:通用語言基礎結構定義了通用語言規範,該規範位於分割區 I、子句 7 到 11。
如果您的元件符合 Common Language Specification,則保證符合 CLS 規範,可以透過任何支援 CLS 的程式設計語言撰寫的組件中的程式碼進行存取。 您可以藉由將 CLSCompliantAttribute 屬性套用至原始程式碼,來判斷元件在編譯時期是否符合 Common Language Specification。 如需詳細資訊,請參閱 CLSCompliantAttribute 屬性。
CLS 合規性規則
本節討論建立符合 CLS 規範元件的規則。 如需規則的完整清單,請參閱 ECMA-335 標準:通用語言基礎結構的分割區 I、子句 11。
備註
Common Language Specification 會討論 CLS 合規性的每個規則,因為它適用於取用者(以程式設計方式存取符合 CLS 規範的元件)、架構(使用語言編譯程式建立符合 CLS 規範連結庫的開發人員),以及擴充器(正在建立語言編譯程式之類的工具的開發人員,或建立 CLS 兼容元件的程式代碼剖析器)。 本文著重於套用至架構的規則。 不過請注意,套用至擴充器的一些規則也可能套用至使用 Reflection.Emit 所建立的元件。
若要設計與語言無關的元件,您只需要將 CLS 合規性的規則套用至元件的公用介面。 您的私人實作不需要符合規格。
這很重要
CLS 合規性的規則僅適用於元件的公用介面,不適用於其私用實作。
例如,Byte 以外的無符號整數不符合CLS標準。 由於下列範例中的 Person 類別會公開 Age類型的 UInt16 屬性,因此下列程式代碼會顯示編譯程式警告。
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,這是符合 CLS 規範的 16 位帶正負號整數,讓 Int16 類別 CLS 相容。 您不需要變更私人 personAge 欄位的類型。
using System;
[assembly: CLSCompliant(true)]
public class Person
{
private UInt16 personAge = 0;
public Int16 Age => (Int16)personAge;
}
<Assembly: CLSCompliant(True)>
Public Class Person
Private personAge As UInt16
Public ReadOnly Property Age As Int16
Get
Return CType(personAge, Int16)
End Get
End Property
End Class
函式庫的公共介面包含下列各項:
公共類別的定義。
公用類別的公用成員定義,以及衍生類別可存取的成員定義(也就是受保護的成員)。
公用類別之公用方法的參數和傳回型別,以及衍生類別可存取之方法的參數和傳回型別。
下表列出 CLS 合規性的規則。 規則的文字是逐字取自 ECMA-335 標準:通用語言基礎結構,並由 Ecma International 於 2012 年著作權所有。 如需這些規則的詳細資訊,請參閱下列各節。
| 類別 | 看! | 規則 | 規則編號 |
|---|---|---|---|
| 可及性 | 成員無障礙設施 | 覆寫繼承的方法時,存取性不得變更,除非覆寫的是繼承自不同組件且具有family-or-assembly的存取性的方法。 在此情況下,覆寫應具有可及性 family。 |
10 |
| 可及性 | 成員無障礙設施 | 型別和成員的可見度和存取性應為 ,如此一來,只要成員本身可見且可存取,任何成員簽章中的型別都應該可見且可存取。 例如,在其元件外部可見的公用方法不應該有一個自變數,其類型只能在元件內顯示。 成員簽章中所使用的已具現化泛型型別的所有組成型別,其可見度和存取性應當在成員本身可見且可存取時,同樣是可見且可存取的。 例如,當一個具現化的泛型型別出現在其組件可見對外的成員簽章中時,不應包含一個其類型僅在該組件內可見的泛型參數。 | 12 |
| 陣列 | 陣列 | 陣列應具有符合 CLS 規範的類型的元素,且陣列的所有維度都應該具有下限為零。 只有項目是陣列且需要根據陣列的元素類型來區分多載才能區別。 當重載是以兩個或多個陣列類型為基礎時,元素類型應為具名類型。 | 16 |
| 屬性 | 屬性 | 屬性的類型應為 System.Attribute,或繼承自它的型別。 | 41 |
| 屬性 | 屬性 | CLS 只允許自訂屬性編碼的子集。 這些編碼中應顯示的唯一類型是 (請參閱分割區 IV):System.Type、System.String、System.Char、System.Boolean、System.Byte、System.Int16、System.Int32、System.Int64、System.Single、System.Double,以及以 CLS 兼容的基底整數類型為基礎的任何列舉型別。 | 34 |
| 屬性 | 屬性 | CLS 不允許公開顯示的必要修飾詞(modreq,請參閱分割區II),但允許其無法理解的選擇性修飾詞(modopt,請參閱分割區II)。 |
35 |
| 建構函數 | 建構函式 | 物件建構函式必須先呼叫其基類的某些實例建構函式,然後才能存取繼承的實例資料。 (這不適用於不需要建構函式的實值型別。 | 21 |
| 建構函數 | 建構函式 | 物件建構函式不得呼叫,除非是物件建立的一部分,而且不應該初始化物件兩次。 | 22 |
| 枚舉 | 列舉 | 列舉的基礎類型應為內建的CLS整數類型,欄位名稱應為 "value__",且該欄位應標記為 RTSpecialName。 |
7 |
| 枚舉 | 列舉 | 有兩種不同的列舉類型,是根據自定義屬性 System.FlagsAttribute 的有無來指示(請參閱分區IV程式庫)。 一個代表具名整數值;另一個代表可合併以產生未命名值的具名位旗標。
enum 的值不限於指定的值。 |
8 |
| 枚舉 | 列舉 | 列舉型別的字面靜態欄位應該具有列舉型別本身的類型。 | 9 |
| 事件 | 活動 | 實作事件的方法應標示為元數據中的 SpecialName。 |
二十九 |
| 事件 | 活動 | 事件及其存取子的可及性應相同。 | 30 |
| 事件 | 活動 | 事件的 add 和 remove 方法應同時存在或不存在。 |
31 |
| 事件 | 活動 | 事件的 add 和 remove 方法各自應接受一個參數,其類型用於定義事件的類型,且必須是從 System.Delegate派生的。 |
32 |
| 事件 | 活動 | 事件應遵守特定的命名模式。 CLS 規則 29 中所參考的 SpecialName 屬性應該在適當的名稱比較中忽略,並遵守標識符規則。 | 33 |
| 例外狀況 | 例外狀況 | 被拋出的物件類型必須是 System.Exception 或繼承自它的型別。 不過,不需要符合 CLS 規範的方法,即可封鎖其他類型的例外狀況傳播。 | 40 |
| 一般 | CLS 合規性規則 | CLS 規則僅適用於定義元件外部可存取或可見的類型部分。 | 1 |
| 一般 | CLS 合規性規則 | 不符合 CLS 規範類型的成員不得標示為符合 CLS 規範。 | 2 |
| 泛型 | 泛型類型和成員 | 巢狀類型至少應具有與封入類型一樣多的泛型參數。 巢狀類型中的泛型參數按位置對應到其封閉類型的泛型參數。 | 42 |
| 泛型 | 泛型類型和成員 | 泛型型別的名稱應根據上面定義的規則,對於非巢狀類型,編碼其上宣告的類型參數數目;若為巢狀類型,則編碼新引入的類型參數數目。 | 43 |
| 泛型 | 泛型類型和成員 | 泛型型別應重新宣告足夠的條件約束,以確保其條件約束能滿足基底型別或介面上的任何條件約束。 | 44 |
| 泛型 | 泛型類型和成員 | 作為泛型參數條件約束的類型本身應符合 CLS 規範。 | 45 |
| 泛型 | 泛型類型和成員 | 具現化泛型型別中成員(包括巢狀類型)的可見度和可存取性,應視為限制於特定的實例化範圍,而不是整個泛型型別宣告。 假設這一點,CLS 規則 12 的可見性和可存取性規則仍適用。 | 46 |
| 泛型 | 泛型類型和成員 | 對於每個抽象或虛擬泛型方法,都應該有預設的具體實作(非抽象的實作) | 47 |
| 介面 | 介面 | 符合 CLS 規範的介面不應要求定義不符合 CLS 規範的方法,才能實作它們。 | 18 |
| 介面 | 介面 | 符合 CLS 規範的介面不得定義靜態方法,也不能定義欄位。 | 19 |
| 成員 | 類型成員概述 | 全域靜態欄位和方法不符合CLS標準。 | 36 |
| 成員 | -- | 常值靜態的值是使用欄位初始化的元數據來指定的。 符合 CLS 標準的常值必須在字段初始化元數據中指定一個值,此值必須與常值的類型完全相同(或者如果常值是 enum,則此值須為其基礎類型)。 |
13 |
| 成員 | 類型成員概述 | vararg 條件約束不是CLS的一部分,而CLS支援的唯一呼叫慣例是標準 Managed 呼叫慣例。 | 15 |
| 命名慣例 | 命名慣例 | 組件應遵循 Unicode Standard 3.0 的技術報告附件 7,管理允許啟動並包含在識別符中的字元集,可在線獲得於 Unicode 正規化形式。 識別碼應採用 Unicode 正規化形式 C 所定義的標準格式。就 CLS 的目的而言,如果兩個識別碼的小寫映射(如 Unicode 中不依賴地區設定且一對一的小寫映射所指定)相同,則它們被視為相同。 在 CLS 下,若要將兩個識別符視為不同,它們必須除了大小寫之外,還在其他方面不同。 不過,為了覆寫繼承的定義,CLI 需要使用原始宣告的精確編碼。 | 4 |
| 重載 | 命名慣例 | 在符合 CLS 規範的範圍中引進的所有名稱,無論種類如何,都應該是唯一的,除非名稱相同並且是透過多載來解析的。 也就是說,雖然 CTS 允許單一類型對方法和欄位使用相同的名稱,但 CLS 則不會。 | 5 |
| 重載 | 命名慣例 | 欄位和巢狀類型應僅通過標識符比較來區分,儘管 CTS 允許區分不同的簽名。 具有相同名稱的方法、屬性和事件(依標識符比較)除了必須在傳回類型之外還要有其他不同之處,除非CLS規則39中另有規定。 | 6 |
| 重載 | 過載 | 只有屬性和方法可以被多載。 | 37 |
| 重載 | 過載 | 屬性和方法只能根據其參數的數目和類型來多載,但名為 op_Implicit 和 op_Explicit的轉換運算元除外,也可以根據其傳回型別多載。 |
38 |
| 重載 | -- | 如果在類型中宣告的兩個或多個符合 CLS 規範的方法具有相同的名稱,而且針對特定的類型具現化集合,它們具有相同的參數和傳回型別,則所有這些方法在語意上應該對等於這些類型具現化。 | 48 |
| 性能 | 屬性 | 實作屬性之 getter 和 setter 方法的方法,應標示為元數據中的 SpecialName。 |
24 |
| 性能 | 屬性 | 屬性的存取子應該全部為靜態、全部為虛擬,或全部為實例。 | 26 |
| 性能 | 屬性 | 屬性的類型應該是 getter 的傳回型別,以及 setter 最後一個自變數的類型。 屬性的參數類型應是 getter 的參數類型,以及除了最後一個參數之外所有 setter 的參數類型。 所有這些類型都應符合 CLS 規範,不得是受管理的指標(即不得以引用方式傳遞)。 | 二十七 |
| 性能 | 屬性 | 屬性應遵守特定的命名模式。 CLS 規則 24 中所參考的 SpecialName 屬性,應該在適當的名稱比較中忽略,並遵守標識符規則。 屬性應該有 getter 方法、setter 方法或兩者。 |
28 |
| 類型轉換 | 類型轉換 | 如果提供op_Implicit或op_Explicit,則應當提供提供強制的替代方式。 | 39 |
| 類型 | 類型和類型成員簽章 | 盒裝實值類型不符合 CLS 兼容性。 | 3 |
| 類型 | 類型和類型成員簽章 | 出現在簽章中的所有類型都應符合CLS標準。 組成具現化泛型型別的所有類型都應該符合 CLS 標準。 | 11 |
| 類型 | 類型和類型成員簽章 | 類型化的引用不符合通用語言規範(CLS)。 | 14 |
| 類型 | 類型和類型成員簽章 | 非受控指標類型不符合CLS標準。 | 17 |
| 類型 | 類型和類型成員簽章 | 符合 CLS 規範的類別、實值類型和介面不應要求實作不符合 CLS 規範的成員 | 20 |
| 類型 | 類型和類型成員簽章 | System.Object 符合 CLS 規範。 任何其他符合 CLS 規範的類別應繼承自符合 CLS 規範的類別。 | 23 |
子區段的索引:
類型和類型成員簽名
System.Object 類型符合 CLS 標準,而且是 .NET 類型系統中所有物件類型的基底類型。 .NET 中的繼承是隱含的(例如,String 類別隱含繼承自 Object 類別)或明確 (例如,CultureNotFoundException 類別明確繼承自 ArgumentException 類別,而該類別會明確繼承自 Exception 類別。 若要讓衍生類型符合 CLS 標準,其基底類型也必須符合 CLS 標準。
下列範例顯示基底類型不符合CLS規範的衍生型別。 它會定義基底 Counter 類別,該類別使用不帶正負號的32位整數作為計數器。 因為類別會包裝不帶正負號的整數來提供計數器功能,因此類別會標示為不符合 CLS 規範。 因此,衍生類別 NonZeroCounter,也不符合CLS標準。
using System;
[assembly: CLSCompliant(true)]
[CLSCompliant(false)]
public class Counter
{
UInt32 ctr;
public Counter()
{
ctr = 0;
}
protected Counter(UInt32 ctr)
{
this.ctr = ctr;
}
public override string ToString()
{
return String.Format("{0}). ", ctr);
}
public UInt32 Value
{
get { return ctr; }
}
public void Increment()
{
ctr += (uint) 1;
}
}
public class NonZeroCounter : Counter
{
public NonZeroCounter(int startIndex) : this((uint) startIndex)
{
}
private NonZeroCounter(UInt32 startIndex) : base(startIndex)
{
}
}
// Compilation produces a compiler warning like the following:
// Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
// CLS-compliant
// Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
<CLSCompliant(False)> _
Public Class Counter
Dim ctr As UInt32
Public Sub New
ctr = 0
End Sub
Protected Sub New(ctr As UInt32)
ctr = ctr
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0}). ", ctr)
End Function
Public ReadOnly Property Value As UInt32
Get
Return ctr
End Get
End Property
Public Sub Increment()
ctr += CType(1, UInt32)
End Sub
End Class
Public Class NonZeroCounter : Inherits Counter
Public Sub New(startIndex As Integer)
MyClass.New(CType(startIndex, UInt32))
End Sub
Private Sub New(startIndex As UInt32)
MyBase.New(CType(startIndex, UInt32))
End Sub
End Class
' Compilation produces a compiler warning like the following:
' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
' because it derives from 'Counter', which is not CLS-compliant.
'
' Public Class NonZeroCounter : Inherits Counter
' ~~~~~~~~~~~~~~
出現在成員簽章中的所有類型,包括方法的傳回類型或屬性類型,都必須符合 CLS 標準。 此外,針對泛型類型:
組成具現化泛型型別的所有類型都必須符合 CLS 標準。
作為泛型參數條件約束的所有類型都必須符合 CLS 標準。
.NET 通用型別系統 包含許多內建類型,這些內建型別直接由 Common Language Runtime 支援,而且會特別編碼在元件的元數據中。 在這些內部類型中,下表所列的類型符合CLS標準。
| 符合 CLS 規範的類型 | 說明 |
|---|---|
| 位元組 | 8 位無符號整數 |
| Int16 | 16 位帶正負號的整數 |
| Int32 | 32 位帶正負號的整數 |
| Int64 | 64 位帶正負號的整數 |
| 半 | 半精度浮點值 |
| 單一 | 單精度浮點數 |
| 雙 | 雙精確度浮點數值 |
| 布爾值 | true 或 false 實值類型 |
| 字符 | UTF-16 編碼的碼元 |
| 十進位 | 非浮點十進位數 |
| IntPtr | 由平台定義大小的指標或句柄 |
| 字串 | 零、一或多個 Char 物件的集合 |
下表所列的內建類型不符合CLS標準。
| 不符合規範的類型 | 說明 | 符合 CLS 規範替代方案 |
|---|---|---|
| SByte | 8 位帶正負號的整數數據類型 | Int16 |
| UInt16 | 16 位無符號整數 | Int32 |
| UInt32 | 32 位無符號整數 | Int64 |
| UInt64 | 64 位無符號整數 | Int64(可能溢位)、BigInteger或 Double |
| UIntPtr | 無符號指標或句柄 | IntPtr |
.NET 類別庫或任何其他類別庫可能包含不符合 CLS 規範的其他類型,例如:
封裝的實值型別。 下列 C# 範例會建立類別,其具有名為
int*Value類型的公用屬性。 因為int*是 Boxed 實值類型,所以編譯程式會將它標示為不符合 CLS 規範。using System; [assembly:CLSCompliant(true)] public unsafe class TestClass { private int* val; public TestClass(int number) { val = (int*) number; } public int* Value { get { return val; } } } // The compiler generates the following output when compiling this example: // warning CS3003: Type of 'TestClass.Value' is not CLS-compliant具類型參照的引用,這是一種特殊的結構體,包含物件的引用和類型的引用。 在 .NET 中,具型別參考由 TypedReference 類別表示。
如果類型不符合 CLS,您應該將 CLSCompliantAttribute 屬性(其 isCompliant 值為 false)套用至該類型。 如需詳細資訊,請參閱 CLSCompliantAttribute 屬性 一節。
下列範例說明在方法簽章和泛型類型具現化中的 CLS 合規性問題。 它定義了 InvoiceItem 類別,具有一個類型為 UInt32的屬性、一個類型為 Nullable<UInt32>的屬性,以及一個具有類型為 UInt32 和 Nullable<UInt32>參數的建構函式。 當您嘗試編譯此範例時,您會收到四個編譯程式警告。
using System;
[assembly: CLSCompliant(true)]
public class InvoiceItem
{
private uint invId = 0;
private uint itemId = 0;
private Nullable<uint> qty;
public InvoiceItem(uint sku, Nullable<uint> quantity)
{
itemId = sku;
qty = quantity;
}
public Nullable<uint> Quantity
{
get { return qty; }
set { qty = value; }
}
public uint InvoiceId
{
get { return invId; }
set { invId = value; }
}
}
// The attempt to compile the example displays the following output:
// Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
// Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
// Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
// Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
<Assembly: CLSCompliant(True)>
Public Class InvoiceItem
Private invId As UInteger = 0
Private itemId As UInteger = 0
Private qty AS Nullable(Of UInteger)
Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
itemId = sku
qty = quantity
End Sub
Public Property Quantity As Nullable(Of UInteger)
Get
Return qty
End Get
Set
qty = value
End Set
End Property
Public Property InvoiceId As UInteger
Get
Return invId
End Get
Set
invId = value
End Set
End Property
End Class
' The attempt to compile the example displays output similar to the following:
' Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~
' Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~~~~~~
' Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Property Quantity As Nullable(Of UInteger)
' ~~~~~~~~
' Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
'
' Public Property InvoiceId As UInteger
' ~~~~~~~~~
若要排除編譯程式警告,請將 InvoiceItem 公用介面中的不符合 CLS 規範的類型取代為符合規範的類型:
using System;
[assembly: CLSCompliant(true)]
public class InvoiceItem
{
private uint invId = 0;
private uint itemId = 0;
private Nullable<int> qty;
public InvoiceItem(int sku, Nullable<int> quantity)
{
if (sku <= 0)
throw new ArgumentOutOfRangeException("The item number is zero or negative.");
itemId = (uint) sku;
qty = quantity;
}
public Nullable<int> Quantity
{
get { return qty; }
set { qty = value; }
}
public int InvoiceId
{
get { return (int) invId; }
set {
if (value <= 0)
throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
invId = (uint) value; }
}
}
<Assembly: CLSCompliant(True)>
Public Class InvoiceItem
Private invId As UInteger = 0
Private itemId As UInteger = 0
Private qty AS Nullable(Of Integer)
Public Sub New(sku As Integer, quantity As Nullable(Of Integer))
If sku <= 0 Then
Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
End If
itemId = CUInt(sku)
qty = quantity
End Sub
Public Property Quantity As Nullable(Of Integer)
Get
Return qty
End Get
Set
qty = value
End Set
End Property
Public Property InvoiceId As Integer
Get
Return CInt(invId)
End Get
Set
invId = CUInt(value)
End Set
End Property
End Class
除了列出的特定類型之外,某些類型的類別不符合CLS標準。 其中包括非受控指標類型和函式指標類型。 下列範例會產生編譯器警告,因為它使用指向整數的指標來建立整數陣列。
using System;
[assembly: CLSCompliant(true)]
public class ArrayHelper
{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant
針對符合 CLS 規範的抽象類(也就是 C# 中標示為 abstract 的類別或 Visual Basic 中的 MustInherit),類別的所有成員也必須符合 CLS 標準。
命名慣例
由於某些程式語言對大小寫不敏感,因此識別符號(例如命名空間、類型和成員的名稱)必須不僅僅是在大小寫上有所區別。 如果小寫對應相同,則會將兩個標識符視為相等。 下列 C# 範例會定義兩個公用類別,Person 和 person。 因為只有大小寫不同,所以 C# 編譯器會將它們標示為不符合 CLS 規範。
using System;
[assembly: CLSCompliant(true)]
public class Person : person
{
}
public class person
{
}
// Compilation produces a compiler warning like the following:
// Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
// only in case is not CLS-compliant
// Naming1.cs(6,14): (Location of symbol related to previous warning)
程序設計語言標識碼,例如命名空間、類型和成員的名稱,必須符合 Unicode Standard 。 這表示:
標識元的第一個字元可以是任何 Unicode 大寫字母、小寫字母、標題大小寫字母、修飾詞字母、其他字母或字母號碼。 如需 Unicode 字元類別的資訊,請參閱 System.Globalization.UnicodeCategory 列舉。
後續字元可以來自任何類別做為第一個字元,也可以包含非間距標記、間距結合標記、十進位數、連接器標點符號和格式化代碼。
比較標識碼之前,您應該篩選掉格式化程序代碼,並將標識符轉換成 Unicode 正規化表單 C,因為單一字元可以由多個 UTF-16 編碼的程式代碼單位來表示。 在 Unicode 正規化表單 C 中產生相同程式代碼單位的字元序列不符合 CLS 標準。 下列範例會定義名為 Å的屬性,其中包含字元 ANGSTROM SIGN (U+212B),以及名為 Å的第二個屬性,其中包含字元 LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5)。 C# 和 Visual Basic 編譯程式都會將原始碼標示為不符合 CLS 規範。
public class Size
{
private double a1;
private double a2;
public double Å
{
get { return a1; }
set { a1 = value; }
}
public double Å
{
get { return a2; }
set { a2 = value; }
}
}
// Compilation produces a compiler warning like the following:
// Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
// CLS-compliant
// Naming2a.cs(10,18): (Location of symbol related to previous warning)
// Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
// CLS-compliant
// Naming2a.cs(12,8): (Location of symbol related to previous warning)
// Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
// CLS-compliant
// Naming2a.cs(13,8): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
Public Class Size
Private a1 As Double
Private a2 As Double
Public Property Å As Double
Get
Return a1
End Get
Set
a1 = value
End Set
End Property
Public Property Å As Double
Get
Return a2
End Get
Set
a2 = value
End Set
End Property
End Class
' Compilation produces a compiler warning like the following:
' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
' with identical signatures.
'
' Public Property Å As Double
' ~
特定範圍中的成員名稱(例如元件內的命名空間、命名空間內的類型,或類型內的成員)必須是唯一的,但透過多載解析的名稱除外。 這項需求比一般類型系統更嚴格,只要這些成員是不同類型的成員,即可讓範圍內的多個成員具有相同的名稱(例如,一個是方法,一個是字段)。 特別是對於類型成員而言:
欄位和巢狀類型是單憑名稱來區分的。
名稱相同的方法、屬性和事件必須不僅僅在傳回類型上有所不同。
成員名稱在其範圍內必須是唯一的要求,下列範例說明這一點。 它會定義名為 Converter 的類別,其中包含名為 Conversion的四個成員。 三個是方法,一個是 屬性。 包含 Int64 參數的方法會是唯一命名的,但具有 Int32 參數的兩個方法不是,因為傳回值不會被視為成員簽章的一部分。
Conversion 屬性也違反這項需求,因為屬性不能與多載方法同名。
using System;
[assembly: CLSCompliant(true)]
public class Converter
{
public double Conversion(int number)
{
return (double) number;
}
public float Conversion(int number)
{
return (float) number;
}
public double Conversion(long number)
{
return (double) number;
}
public bool Conversion
{
get { return true; }
}
}
// Compilation produces a compiler error like the following:
// Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
// 'Conversion' with the same parameter types
// Naming3.cs(8,18): (Location of symbol related to previous error)
// Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
// 'Conversion'
// Naming3.cs(8,18): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>
Public Class Converter
Public Function Conversion(number As Integer) As Double
Return CDbl(number)
End Function
Public Function Conversion(number As Integer) As Single
Return CSng(number)
End Function
Public Function Conversion(number As Long) As Double
Return CDbl(number)
End Function
Public ReadOnly Property Conversion As Boolean
Get
Return True
End Get
End Property
End Class
' Compilation produces a compiler error like the following:
' Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double'
' and 'Public Function Conversion(number As Integer) As Single' cannot
' overload each other because they differ only by return types.
'
' Public Function Conversion(number As Integer) As Double
' ~~~~~~~~~~
' Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function
' Conversion(number As Integer) As Single' in this class.
'
' Public ReadOnly Property Conversion As Boolean
' ~~~~~~~~~~
個別語言包含唯一關鍵詞,因此以 Common Language Runtime 為目標的語言也必須提供一些機制來參考與關鍵詞相吻合的標識碼(例如類型名稱)。 例如,case 是 C# 和 Visual Basic 中的關鍵詞。 不過,下列 Visual Basic 範例可以使用左括弧和右大括弧,將名為 case 的類別從 case 關鍵詞中釐清。 否則,此範例會產生錯誤訊息:「關鍵詞無效為標識符」,且無法編譯。
Public Class [case]
Private _id As Guid
Private name As String
Public Sub New(name As String)
_id = Guid.NewGuid()
Me.name = name
End Sub
Public ReadOnly Property ClientName As String
Get
Return name
End Get
End Property
End Class
下列 C# 範例可以使用 case 符號來將標識符與語言關鍵字區分開,進而實例化 @ 類別。 如果沒有它,C# 編譯器會顯示兩個錯誤訊息:「預期類型」和「無效的運算式詞彙 'case'」。
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
類型轉換
Common Language Specification 定義兩個轉換運算符:
op_Implicit,用於擴大不會造成數據或精確度遺失的轉換。 例如,Decimal 結構包含多載op_Implicit運算符,可將整數型別的值和 Char 值轉換成 Decimal 值。op_Explicit,用於縮小轉換,可能會導致數值大小損失(將值轉換為範圍較小的值)或精度降低。 例如,Decimal 結構包含多載op_Explicit運算符,可將 Double 和 Single 值轉換成 Decimal,以及將 Decimal 值轉換成整數值、Double、Single和 Char。
不過,並非所有語言都支援運算元多載或自定義運算符的定義。 如果您選擇實作這些轉換運算符,則也應該提供替代方式來執行轉換。 我們建議您提供 FromXxx 和 ToXxx 方法。
下列範例會定義符合CLS規範的隱含和明確轉換。 它會建立 UDouble 類別,代表不帶正負號、雙精確度、浮點數。 它提供從 UDouble 到 Double 的隱含轉換,以及從 UDouble 到 Single、Double 到 UDouble的明確轉換,以及將 Single 轉換成 UDouble。 它也會將 ToDouble 方法定義為隱含轉換運算符和 ToSingle、FromDouble和 FromSingle 方法的替代方法,作為明確轉換運算符的替代方案。
using System;
public struct UDouble
{
private double number;
public UDouble(double value)
{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
number = value;
}
public UDouble(float value)
{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
number = value;
}
public static readonly UDouble MinValue = (UDouble) 0.0;
public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;
public static explicit operator Double(UDouble value)
{
return value.number;
}
public static implicit operator Single(UDouble value)
{
if (value.number > (double) Single.MaxValue)
throw new InvalidCastException("A UDouble value is out of range of the Single type.");
return (float) value.number;
}
public static explicit operator UDouble(double value)
{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
return new UDouble(value);
}
public static implicit operator UDouble(float value)
{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");
return new UDouble(value);
}
public static Double ToDouble(UDouble value)
{
return (Double) value;
}
public static float ToSingle(UDouble value)
{
return (float) value;
}
public static UDouble FromDouble(double value)
{
return new UDouble(value);
}
public static UDouble FromSingle(float value)
{
return new UDouble(value);
}
}
Public Structure UDouble
Private number As Double
Public Sub New(value As Double)
If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub
Public Sub New(value As Single)
If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub
Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)
Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue
Public Shared Widening Operator CType(value As UDouble) As Double
Return value.number
End Operator
Public Shared Narrowing Operator CType(value As UDouble) As Single
If value.number > CDbl(Single.MaxValue) Then
Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
End If
Return CSng(value.number)
End Operator
Public Shared Narrowing Operator CType(value As Double) As UDouble
If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator
Public Shared Narrowing Operator CType(value As Single) As UDouble
If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator
Public Shared Function ToDouble(value As UDouble) As Double
Return CType(value, Double)
End Function
Public Shared Function ToSingle(value As UDouble) As Single
Return CType(value, Single)
End Function
Public Shared Function FromDouble(value As Double) As UDouble
Return New UDouble(value)
End Function
Public Shared Function FromSingle(value As Single) As UDouble
Return New UDouble(value)
End Function
End Structure
陣列
符合 CLS 規範的陣列遵循以下規則:
陣列的所有維度都必須有零的下限。 下列範例會建立不符合CLS規範的陣列,其下限為一。 儘管 CLSCompliantAttribute 屬性存在,但編譯程式不會偵測到
Numbers.GetTenPrimes方法傳回的陣列不符合 CLS 標準。[assembly: CLSCompliant(true)] public class Numbers { public static Array GetTenPrimes() { Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1}); arr.SetValue(1, 1); arr.SetValue(2, 2); arr.SetValue(3, 3); arr.SetValue(5, 4); arr.SetValue(7, 5); arr.SetValue(11, 6); arr.SetValue(13, 7); arr.SetValue(17, 8); arr.SetValue(19, 9); arr.SetValue(23, 10); return arr; } }<Assembly: CLSCompliant(True)> Public Class Numbers Public Shared Function GetTenPrimes() As Array Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1}) arr.SetValue(1, 1) arr.SetValue(2, 2) arr.SetValue(3, 3) arr.SetValue(5, 4) arr.SetValue(7, 5) arr.SetValue(11, 6) arr.SetValue(13, 7) arr.SetValue(17, 8) arr.SetValue(19, 9) arr.SetValue(23, 10) Return arr End Function End Class所有陣列元素都必須由符合 CLS 規範的類型組成。 下列範例定義了兩個傳回不符合 CLS 規範的陣列的方法。 第一個會傳回一個 UInt32 值的陣列。 第二個會傳回包含 Object 和 Int32 值的 UInt32 陣列。 雖然編譯程式因為 UInt32 類型而將第一個陣列識別為不符合規範,但它無法辨識出第二個陣列包含不符合 CLS 規範的元素。
using System; [assembly: CLSCompliant(true)] public class Numbers { public static UInt32[] GetTenPrimes() { uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u }; return arr; } public static Object[] GetFivePrimes() { Object[] arr = { 1, 2, 3, 5u, 7u }; return arr; } } // Compilation produces a compiler warning like the following: // Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not // CLS-compliant<Assembly: CLSCompliant(True)> Public Class Numbers Public Shared Function GetTenPrimes() As UInt32() Return {1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui} End Function Public Shared Function GetFivePrimes() As Object() Dim arr() As Object = {1, 2, 3, 5ui, 7ui} Return arr End Function End Class ' Compilation produces a compiler warning like the following: ' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant. ' ' Public Shared Function GetTenPrimes() As UInt32() ' ~~~~~~~~~~~~具有陣列參數之方法的多載解析是以它們為陣列和其元素類型的事實為基礎。 因此,下列多載
GetSquares方法的定義符合CLS規範。using System; using System.Numerics; [assembly: CLSCompliant(true)] public class Numbers { public static byte[] GetSquares(byte[] numbers) { byte[] numbersOut = new byte[numbers.Length]; for (int ctr = 0; ctr < numbers.Length; ctr++) { int square = ((int) numbers[ctr]) * ((int) numbers[ctr]); if (square <= Byte.MaxValue) numbersOut[ctr] = (byte) square; // If there's an overflow, assign MaxValue to the corresponding // element. else numbersOut[ctr] = Byte.MaxValue; } return numbersOut; } public static BigInteger[] GetSquares(BigInteger[] numbers) { BigInteger[] numbersOut = new BigInteger[numbers.Length]; for (int ctr = 0; ctr < numbers.Length; ctr++) numbersOut[ctr] = numbers[ctr] * numbers[ctr]; return numbersOut; } }Imports System.Numerics <Assembly: CLSCompliant(True)> Public Module Numbers Public Function GetSquares(numbers As Byte()) As Byte() Dim numbersOut(numbers.Length - 1) As Byte For ctr As Integer = 0 To numbers.Length - 1 Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr))) If square <= Byte.MaxValue Then numbersOut(ctr) = CByte(square) ' If there's an overflow, assign MaxValue to the corresponding ' element. Else numbersOut(ctr) = Byte.MaxValue End If Next Return numbersOut End Function Public Function GetSquares(numbers As BigInteger()) As BigInteger() Dim numbersOut(numbers.Length - 1) As BigInteger For ctr As Integer = 0 To numbers.Length - 1 numbersOut(ctr) = numbers(ctr) * numbers(ctr) Next Return numbersOut End Function End Module
介面
符合 CLS 標準的介面可以定義屬性、事件和虛擬方法(沒有實作的方法)。 符合 CLS 規範的介面不能有下列任一項:
靜態方法或靜態欄位。 如果您在介面中定義靜態成員,C# 和 Visual Basic 編譯程式都會產生編譯程序錯誤。
領域。 如果您在介面中定義欄位,C# 和 Visual Basic 編譯程式都會產生編譯程式錯誤。
不符合 CLS 規範的技術方法。 例如,下列介面定義包含方法,
INumber.GetUnsigned,標示為不符合CLS規範。 此範例會產生編譯程式警告。using System; [assembly:CLSCompliant(true)] public interface INumber { int Length(); [CLSCompliant(false)] ulong GetUnsigned(); } // Attempting to compile the example displays output like the following: // Interface2.cs(8,32): warning CS3010: 'INumber.GetUnsigned()': CLS-compliant interfaces // must have only CLS-compliant members<Assembly: CLSCompliant(True)> Public Interface INumber Function Length As Integer <CLSCompliant(False)> Function GetUnsigned As ULong End Interface ' Attempting to compile the example displays output like the following: ' Interface2.vb(9) : warning BC40033: Non CLS-compliant 'function' is not allowed in a ' CLS-compliant interface. ' ' <CLSCompliant(False)> Function GetUnsigned As ULong ' ~~~~~~~~~~~由於此規則,符合CLS規範的類型不需要實作不符合CLS規範的成員。 如果符合 CLS 規範的架構確實會公開實作不符合 CLS 規範介面的類別,它也應該提供所有不符合 CLS 規範成員的具體實作。
符合 CLS 規範的語言編譯程式也必須允許類別提供在多個介面中具有相同名稱和簽章之成員的個別實作。 C# 和 Visual Basic 都支援 明確的介面實作,以提供相同命名方法的不同實作。 Visual Basic 也支援 Implements 關鍵詞,可讓您明確指定特定成員實作的介面和成員。 下列範例說明此案例,其方式是定義將 Temperature 和 ICelsius 介面實作為明確介面實作的 IFahrenheit 類別。
using System;
[assembly: CLSCompliant(true)]
public interface IFahrenheit
{
decimal GetTemperature();
}
public interface ICelsius
{
decimal GetTemperature();
}
public class Temperature : ICelsius, IFahrenheit
{
private decimal _value;
public Temperature(decimal value)
{
// We assume that this is the Celsius value.
_value = value;
}
decimal IFahrenheit.GetTemperature()
{
return _value * 9 / 5 + 32;
}
decimal ICelsius.GetTemperature()
{
return _value;
}
}
public class Example
{
public static void Main()
{
Temperature temp = new Temperature(100.0m);
ICelsius cTemp = temp;
IFahrenheit fTemp = temp;
Console.WriteLine($"Temperature in Celsius: {cTemp.GetTemperature()} degrees");
Console.WriteLine($"Temperature in Fahrenheit: {fTemp.GetTemperature()} degrees");
}
}
// The example displays the following output:
// Temperature in Celsius: 100.0 degrees
// Temperature in Fahrenheit: 212.0 degrees
<Assembly: CLSCompliant(True)>
Public Interface IFahrenheit
Function GetTemperature() As Decimal
End Interface
Public Interface ICelsius
Function GetTemperature() As Decimal
End Interface
Public Class Temperature : Implements ICelsius, IFahrenheit
Private _value As Decimal
Public Sub New(value As Decimal)
' We assume that this is the Celsius value.
_value = value
End Sub
Public Function GetFahrenheit() As Decimal _
Implements IFahrenheit.GetTemperature
Return _value * 9 / 5 + 32
End Function
Public Function GetCelsius() As Decimal _
Implements ICelsius.GetTemperature
Return _value
End Function
End Class
Module Example
Public Sub Main()
Dim temp As New Temperature(100.0d)
Console.WriteLine("Temperature in Celsius: {0} degrees",
temp.GetCelsius())
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
temp.GetFahrenheit())
End Sub
End Module
' The example displays the following output:
' Temperature in Celsius: 100.0 degrees
' Temperature in Fahrenheit: 212.0 degrees
枚舉
符合 CLS 規範的列舉必須遵循下列規則:
列舉的基礎類型必須是符合內部 CLS 標準的整數(Byte、Int16、Int32或 Int64)。 例如,下列程式碼嘗試定義一個列舉,其基礎類型為 UInt32,並會產生編譯器警告。
using System; [assembly: CLSCompliant(true)] public enum Size : uint { Unspecified = 0, XSmall = 1, Small = 2, Medium = 3, Large = 4, XLarge = 5 }; public class Clothing { public string Name; public string Type; public string Size; } // The attempt to compile the example displays a compiler warning like the following: // Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant<Assembly: CLSCompliant(True)> Public Enum Size As UInt32 Unspecified = 0 XSmall = 1 Small = 2 Medium = 3 Large = 4 XLarge = 5 End Enum Public Class Clothing Public Name As String Public Type As String Public Size As Size End Class ' The attempt to compile the example displays a compiler warning like the following: ' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant. ' ' Public Enum Size As UInt32 ' ~~~~列舉型別必須具有名為
Value__的單一實例欄位,且該欄位會以 FieldAttributes.RTSpecialName 屬性標示。 這可讓您隱式引用欄位值。列舉包含常值靜態字段,其類型符合列舉本身的類型。 例如,如果您使用
State和State.On值定義State.Off列舉,State.On和State.Off都是類型為State的常值靜態字段。列舉有兩種:
列舉,表示一組具名的互斥整數值。 此類型的列舉是由缺少 System.FlagsAttribute 自定義屬性來表示。
列舉表示一組位元旗標,可以結合起來產生未命名的值。 此類型的列舉是以 System.FlagsAttribute 自定義屬性的存在來表示。
如需詳細資訊,請參閱 Enum 結構的文件。
列舉的值不限於其指定值的範圍。 換句話說,列舉中的值範圍是其基礎值的範圍。 您可以使用 Enum.IsDefined 方法來判斷指定的值是否為列舉的成員。
一般型別成員
公共語言規範要求所有欄位和方法必須以特定類別成員的身分存取。 因此,全域靜態字段和方法(也就是除了類型以外定義的靜態字段或方法)不符合 CLS 標準。 如果您嘗試在原始程式碼中包含全域字段或方法,C# 和 Visual Basic 編譯程式都會產生編譯程式錯誤。
Common Language Specification 僅支持標準 Managed 呼叫慣例。 它不支援未受管理的呼叫慣例和方法,其變數自變數清單會以 varargs 關鍵詞標示。 對於與標準 Managed 呼叫慣例相容的變數自變數清單,請使用 ParamArrayAttribute 屬性或個別語言的實作,例如 C# 中的 params 關鍵詞,以及 Visual Basic 中的 ParamArray 關鍵詞。
成員存取範圍
覆寫繼承的成員無法變更該成員的存取範圍。 例如,基類中的公用方法不能被衍生類別中的私用方法覆蓋。 有一個例外狀況:在一個元件中的 protected internal(在 C# 中)或 Protected Friend(在 Visual Basic 中)成員,這些成員是由不同元件中的類型覆寫。 在這種情況下,覆寫的存取性是 Protected。
下列範例說明當 CLSCompliantAttribute 屬性設定為 true時所產生的錯誤,而 Human,這是衍生自 Animal的類別,會嘗試將 Species 屬性的存取範圍從公用變更為私用。 範例在可存取性變更為公開時成功編譯。
using System;
[assembly: CLSCompliant(true)]
public class Animal
{
private string _species;
public Animal(string species)
{
_species = species;
}
public virtual string Species
{
get { return _species; }
}
public override string ToString()
{
return _species;
}
}
public class Human : Animal
{
private string _name;
public Human(string name) : base("Homo Sapiens")
{
_name = name;
}
public string Name
{
get { return _name; }
}
private override string Species
{
get { return base.Species; }
}
public override string ToString()
{
return _name;
}
}
public class Example
{
public static void Main()
{
Human p = new Human("John");
Console.WriteLine(p.Species);
Console.WriteLine(p.ToString());
}
}
// The example displays the following output:
// error CS0621: 'Human.Species': virtual or abstract members cannot be private
<Assembly: CLSCompliant(True)>
Public Class Animal
Private _species As String
Public Sub New(species As String)
_species = species
End Sub
Public Overridable ReadOnly Property Species As String
Get
Return _species
End Get
End Property
Public Overrides Function ToString() As String
Return _species
End Function
End Class
Public Class Human : Inherits Animal
Private _name As String
Public Sub New(name As String)
MyBase.New("Homo Sapiens")
_name = name
End Sub
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Private Overrides ReadOnly Property Species As String
Get
Return MyBase.Species
End Get
End Property
Public Overrides Function ToString() As String
Return _name
End Function
End Class
Public Module Example
Public Sub Main()
Dim p As New Human("John")
Console.WriteLine(p.Species)
Console.WriteLine(p.ToString())
End Sub
End Module
' The example displays the following output:
' 'Private Overrides ReadOnly Property Species As String' cannot override
' 'Public Overridable ReadOnly Property Species As String' because
' they have different access levels.
'
' Private Overrides ReadOnly Property Species As String
成員簽章中的型別必須在該成員可供存取時也能被存取。 例如,這表示公用成員不能包含類型為私用、受保護或內部的參數。 下列範例說明當 StringWrapper 類別的建構函式公開了一個用來決定如何包裝字串值的內部 StringOperationType 列舉值時,會產生的編譯時錯誤。
using System;
using System.Text;
public class StringWrapper
{
string internalString;
StringBuilder internalSB = null;
bool useSB = false;
public StringWrapper(StringOperationType type)
{
if (type == StringOperationType.Normal) {
useSB = false;
}
else {
useSB = true;
internalSB = new StringBuilder();
}
}
// The remaining source code...
}
internal enum StringOperationType { Normal, Dynamic }
// The attempt to compile the example displays the following output:
// error CS0051: Inconsistent accessibility: parameter type
// 'StringOperationType' is less accessible than method
// 'StringWrapper.StringWrapper(StringOperationType)'
Imports System.Text
<Assembly: CLSCompliant(True)>
Public Class StringWrapper
Dim internalString As String
Dim internalSB As StringBuilder = Nothing
Dim useSB As Boolean = False
Public Sub New(type As StringOperationType)
If type = StringOperationType.Normal Then
useSB = False
Else
internalSB = New StringBuilder()
useSB = True
End If
End Sub
' The remaining source code...
End Class
Friend Enum StringOperationType As Integer
Normal = 0
Dynamic = 1
End Enum
' The attempt to compile the example displays the following output:
' error BC30909: 'type' cannot expose type 'StringOperationType'
' outside the project through class 'StringWrapper'.
'
' Public Sub New(type As StringOperationType)
' ~~~~~~~~~~~~~~~~~~~
泛型類型和成員
巢狀類型一律至少有與封入類型一樣多的泛型參數。 這些元素會對應於封入類型中的泛型參數的位置。 泛型類型也可以包含新的泛型參數。
包含類型的泛型類型參數與其內嵌類型之間的關係,可能會被各種語言的語法所隱藏。 在下列範例中,泛型類型 Outer<T> 包含兩個巢狀類別,Inner1A 和 Inner1B<U>。 對 ToString 方法的呼叫顯示,每個類別繼承自 Object.ToString(),並且每個內嵌類別包含其外部類別的類型參數。
using System;
[assembly:CLSCompliant(true)]
public class Outer<T>
{
T value;
public Outer(T value)
{
this.value = value;
}
public class Inner1A : Outer<T>
{
public Inner1A(T value) : base(value)
{ }
}
public class Inner1B<U> : Outer<T>
{
U value2;
public Inner1B(T value1, U value2) : base(value1)
{
this.value2 = value2;
}
}
}
public class Example
{
public static void Main()
{
var inst1 = new Outer<String>("This");
Console.WriteLine(inst1);
var inst2 = new Outer<String>.Inner1A("Another");
Console.WriteLine(inst2);
var inst3 = new Outer<String>.Inner1B<int>("That", 2);
Console.WriteLine(inst3);
}
}
// The example displays the following output:
// Outer`1[System.String]
// Outer`1+Inner1A[System.String]
// Outer`1+Inner1B`1[System.String,System.Int32]
<Assembly: CLSCompliant(True)>
Public Class Outer(Of T)
Dim value As T
Public Sub New(value As T)
Me.value = value
End Sub
Public Class Inner1A : Inherits Outer(Of T)
Public Sub New(value As T)
MyBase.New(value)
End Sub
End Class
Public Class Inner1B(Of U) : Inherits Outer(Of T)
Dim value2 As U
Public Sub New(value1 As T, value2 As U)
MyBase.New(value1)
Me.value2 = value2
End Sub
End Class
End Class
Public Module Example
Public Sub Main()
Dim inst1 As New Outer(Of String)("This")
Console.WriteLine(inst1)
Dim inst2 As New Outer(Of String).Inner1A("Another")
Console.WriteLine(inst2)
Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)
Console.WriteLine(inst3)
End Sub
End Module
' The example displays the following output:
' Outer`1[System.String]
' Outer`1+Inner1A[System.String]
' Outer`1+Inner1B`1[System.String,System.Int32]
泛型型別名稱會以 name'n編碼,其中 名稱 是類型名稱、' 是字元常值,而 n 是類型上宣告的參數數目,或針對巢狀泛型類型,新引進的類型參數數目。 泛型型別名稱的編碼主要對使用反射來存取程式庫中 CLS 相容泛型類型的開發人員有興趣。
如果條件約束套用至泛型型別,則任何做為條件約束的類型也必須符合 CLS 規範。 下列範例會定義一個名為 BaseClass 的類別,該類別不符合 CLS 規範,還有一個名為 BaseCollection 的泛型類別,該類別的類型參數必須衍生自 BaseClass。 但由於 BaseClass 不符合 CLS 標準,編譯程式會發出警告。
using System;
[assembly:CLSCompliant(true)]
[CLSCompliant(false)] public class BaseClass
{}
public class BaseCollection<T> where T : BaseClass
{}
// Attempting to compile the example displays the following output:
// warning CS3024: Constraint type 'BaseClass' is not CLS-compliant
<Assembly: CLSCompliant(True)>
<CLSCompliant(False)> Public Class BaseClass
End Class
Public Class BaseCollection(Of T As BaseClass)
End Class
' Attempting to compile the example displays the following output:
' warning BC40040: Generic parameter constraint type 'BaseClass' is not
' CLS-compliant.
'
' Public Class BaseCollection(Of T As BaseClass)
' ~~~~~~~~~
如果泛型型別衍生自泛型基底類型,則必須重新宣告任何條件約束,才能保證基底類型的條件約束也會滿足。 下列範例會定義可代表任何數值類型的 Number<T>。 它也會定義代表浮點值的 FloatingPoint<T> 類別。 不過,原始程式碼無法編譯,因為它沒有將條件約束(T 必須是實值型別)套用到 Number<T>上的 FloatingPoint<T>。
using System;
[assembly:CLSCompliant(true)]
public class Number<T> where T : struct
{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;
public Number(T value)
{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}
public T Add(T value)
{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}
public T Subtract(T value)
{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}
public class FloatingPoint<T> : Number<T>
{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to compile the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly: CLSCompliant(True)>
Public Class Number(Of T As Structure)
' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double
Public Sub New(value As T)
Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub
Public Function Add(value As T) As T
Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function
Public Function Subtract(value As T) As T
Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class
Public Class FloatingPoint(Of T) : Inherits Number(Of T)
Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to compile the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~
如果條件約束新增至 FloatingPoint<T> 類別,則此範例會成功編譯。
using System;
[assembly:CLSCompliant(true)]
public class Number<T> where T : struct
{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;
public Number(T value)
{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}
public T Add(T value)
{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}
public T Subtract(T value)
{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}
public class FloatingPoint<T> : Number<T> where T : struct
{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly: CLSCompliant(True)>
Public Class Number(Of T As Structure)
' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double
Public Sub New(value As T)
Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub
Public Function Add(value As T) As T
Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function
Public Function Subtract(value As T) As T
Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class
Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)
Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
Common Language Specification 會針對巢狀類型和受保護的成員強制執行保守的個別具現化模型。 開放式泛型類型無法公開具有特定具現化的巢狀受保護泛型型別之簽名的字段或成員。 擴充泛型基類或介面之特定具現化的非泛型型別,無法公開包含巢狀受保護泛型型別不同具現化的字段或成員。
下列範例會定義泛型類型、C1<T>(或 Visual Basic 中的 C1(Of T)),以及受保護的類別 C1<T>.N (或 Visual Basic 中的 C1(Of T).N)。
C1<T> 有兩種方法,M1 和 M2。 不過,M1 不符合CLS標準,因為它會嘗試從 C1C1<int>.NTC1(Of Integer).N (或 <) 傳回 > (或 C1(Of T)) 物件。 第二個類別 C2衍生自 C1<long> (或 C1(Of Long))。 它有兩種方法,M3 和 M4。
M3 不符合 CLS 標準,因為它會嘗試從 C1<int>.N的子類別傳回 C1(Of Integer).N (或 C1<long>) 物件。 語言編譯程式可能更嚴格。 在此範例中,Visual Basic 會在嘗試編譯 M4時顯示錯誤。
using System;
[assembly:CLSCompliant(true)]
public class C1<T>
{
protected class N { }
protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not
// accessible from within C1<T> in all
// languages
protected void M2(C1<T>.N n) { } // CLS-compliant – C1<T>.N accessible
// inside C1<T>
}
public class C2 : C1<long>
{
protected void M3(C1<int>.N n) { } // Not CLS-compliant – C1<int>.N is not
// accessible in C2 (extends C1<long>)
protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is
// accessible in C2 (extends C1<long>)
}
// Attempting to compile the example displays output like the following:
// Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
// Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
<Assembly: CLSCompliant(True)>
Public Class C1(Of T)
Protected Class N
End Class
Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' accessible from within C1(Of T) in all
End Sub ' languages
Protected Sub M2(n As C1(Of T).N) ' CLS-compliant – C1(Of T).N accessible
End Sub ' inside C1(Of T)
End Class
Public Class C2 : Inherits C1(Of Long)
Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant – C1(Of Integer).N is not
End Sub ' accessible in C2 (extends C1(Of Long))
Protected Sub M4(n As C1(Of Long).N)
End Sub
End Class
' Attempting to compile the example displays output like the following:
' error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace
' '<Default>' through class 'C1'.
'
' Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' ~~~~~~~~~~~~~~~~
' error BC30389: 'C1(Of T).N' is not accessible in this context because
' it is 'Protected'.
'
' Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant - C1(Of Integer).N is not
'
' ~~~~~~~~~~~~~~~~
'
' error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
'
' Protected Sub M4(n As C1(Of Long).N)
' ~~~~~~~~~~~~~
建構函數
符合 CLS 規範類別和結構的建構函式必須遵循下列規則:
衍生類別的建構函式必須先呼叫其基類的實例建構函式,才能存取繼承的實例數據。 此需求是因為基類建構函式不會由其衍生類別繼承。 此規則不適用於不支援直接繼承的結構。
一般而言,編譯程式會獨立於CLS合規性強制執行此規則,如下列範例所示。 它會建立衍生自
Doctor類別的Person類別,但Doctor類別無法呼叫Person類別建構函式來初始化繼承的實例字段。using System; [assembly: CLSCompliant(true)] public class Person { private string fName, lName, _id; public Person(string firstName, string lastName, string id) { if (String.IsNullOrEmpty(firstName + lastName)) throw new ArgumentNullException("Either a first name or a last name must be provided."); fName = firstName; lName = lastName; _id = id; } public string FirstName { get { return fName; } } public string LastName { get { return lName; } } public string Id { get { return _id; } } public override string ToString() { return String.Format("{0}{1}{2}", fName, String.IsNullOrEmpty(fName) ? "" : " ", lName); } } public class Doctor : Person { public Doctor(string firstName, string lastName, string id) { } public override string ToString() { return "Dr. " + base.ToString(); } } // Attempting to compile the example displays output like the following: // ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0 // arguments // ctor1.cs(10,11): (Location of symbol related to previous error)<Assembly: CLSCompliant(True)> Public Class Person Private fName, lName, _id As String Public Sub New(firstName As String, lastName As String, id As String) If String.IsNullOrEmpty(firstName + lastName) Then Throw New ArgumentNullException("Either a first name or a last name must be provided.") End If fName = firstName lName = lastName _id = id End Sub Public ReadOnly Property FirstName As String Get Return fName End Get End Property Public ReadOnly Property LastName As String Get Return lName End Get End Property Public ReadOnly Property Id As String Get Return _id End Get End Property Public Overrides Function ToString() As String Return String.Format("{0}{1}{2}", fName, If(String.IsNullOrEmpty(fName), "", " "), lName) End Function End Class Public Class Doctor : Inherits Person Public Sub New(firstName As String, lastName As String, id As String) End Sub Public Overrides Function ToString() As String Return "Dr. " + MyBase.ToString() End Function End Class ' Attempting to compile the example displays output like the following: ' Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call ' to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does ' not have an accessible 'Sub New' that can be called with no arguments. ' ' Public Sub New() ' ~~~除了建立物件之外,無法呼叫物件建構函式。 此外,無法初始化 物件兩次。 例如,這表示 Object.MemberwiseClone 和還原串行化方法不得呼叫建構函式。
性能
符合 CLS 規範類型的屬性必須遵循下列規則:
屬性必須要有其中之一:setter、getter 或兩者。 在元件中,這些會實作為特殊方法,這表示它們會呈現為獨立的方法(getter 方法命名為
get_propertyname,setter 方法命名為set_propertyname),在元件的中繼資料中標記為SpecialName。 C# 和 Visual Basic 編譯程式會自動強制執行此規則,而不需要套用 CLSCompliantAttribute 屬性。屬性的類型是屬性 getter 的傳回型別和 setter 的最後一個自變數。 這些類型必須符合 CLS 標準,而且參數不能以傳址方式指派給屬性(也就是說,它們不能是受控指標)。
如果屬性同時具有 getter 和 setter,它們必須都是虛擬的、都是靜態的,或都是實例屬性。 C# 和 Visual Basic 編譯程式會透過其屬性定義語法自動強制執行此規則。
事件
事件是由其名稱及其類型所定義。 事件類型使用一個委派來表示該事件。 例如,AppDomain.AssemblyResolve 事件的類型為 ResolveEventHandler。 除了事件本身之外,以事件名稱為基礎的三種方法會提供事件的實作,而且在元件的元數據中標示為 SpecialName:
新增事件處理程式的方法,名為
add_EventName。 例如,AppDomain.AssemblyResolve 事件的訂閱方法命名為add_AssemblyResolve。移除事件處理程式的方法,名為
remove_EventName。 例如,AppDomain.AssemblyResolve 事件的移除方法會命名為remove_AssemblyResolve。表示已發生事件的方法,名為
raise_EventName。
備註
大部分關於事件的 Common Language Specification 規則都是由語言編譯程式實作,而且對元件開發人員而言是透明的。
新增、移除和引發事件的方法必須具有相同的存取權限。 它們也必須全部是靜態、實例或虛擬。 加入和移除事件的方法有一個參數,其類型為事件委派類型。 新增和移除方法必須同時存在或兩者都不存在。
下列範例會定義名為 Temperature 的 CLS 相容類別,如果兩個讀數之間的溫度變化等於或超過閾值,就會引發 TemperatureChanged 事件。
Temperature 類別會明確定義 raise_TemperatureChanged 方法,以便選擇性地執行事件處理程式。
using System;
using System.Collections;
using System.Collections.Generic;
[assembly: CLSCompliant(true)]
public class TemperatureChangedEventArgs : EventArgs
{
private Decimal originalTemp;
private Decimal newTemp;
private DateTimeOffset when;
public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)
{
originalTemp = original;
newTemp = @new;
when = time;
}
public Decimal OldTemperature
{
get { return originalTemp; }
}
public Decimal CurrentTemperature
{
get { return newTemp; }
}
public DateTimeOffset Time
{
get { return when; }
}
}
public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);
public class Temperature
{
private struct TemperatureInfo
{
public Decimal Temperature;
public DateTimeOffset Recorded;
}
public event TemperatureChanged TemperatureChanged;
private Decimal previous;
private Decimal current;
private Decimal tolerance;
private List<TemperatureInfo> tis = new List<TemperatureInfo>();
public Temperature(Decimal temperature, Decimal tolerance)
{
current = temperature;
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = temperature;
tis.Add(ti);
ti.Recorded = DateTimeOffset.UtcNow;
this.tolerance = tolerance;
}
public Decimal CurrentTemperature
{
get { return current; }
set {
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = value;
ti.Recorded = DateTimeOffset.UtcNow;
previous = current;
current = value;
if (Math.Abs(current - previous) >= tolerance)
raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
}
}
public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)
{
if (TemperatureChanged == null)
return;
foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {
if (d.Method.Name.Contains("Duplicate"))
Console.WriteLine("Duplicate event handler; event handler not executed.");
else
d.Invoke(this, eventArgs);
}
}
}
public class Example
{
public Temperature temp;
public static void Main()
{
Example ex = new Example();
}
public Example()
{
temp = new Temperature(65, 3);
temp.TemperatureChanged += this.TemperatureNotification;
RecordTemperatures();
Example ex = new Example(temp);
ex.RecordTemperatures();
}
public Example(Temperature t)
{
temp = t;
RecordTemperatures();
}
public void RecordTemperatures()
{
temp.TemperatureChanged += this.DuplicateTemperatureNotification;
temp.CurrentTemperature = 66;
temp.CurrentTemperature = 63;
}
internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Notification 1: The temperature changed from {e.OldTemperature} to {e.CurrentTemperature}");
}
public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Notification 2: The temperature changed from {e.OldTemperature} to {e.CurrentTemperature}");
}
}
Imports System.Collections
Imports System.Collections.Generic
<Assembly: CLSCompliant(True)>
Public Class TemperatureChangedEventArgs : Inherits EventArgs
Private originalTemp As Decimal
Private newTemp As Decimal
Private [when] As DateTimeOffset
Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)
originalTemp = original
newTemp = [new]
[when] = [time]
End Sub
Public ReadOnly Property OldTemperature As Decimal
Get
Return originalTemp
End Get
End Property
Public ReadOnly Property CurrentTemperature As Decimal
Get
Return newTemp
End Get
End Property
Public ReadOnly Property [Time] As DateTimeOffset
Get
Return [when]
End Get
End Property
End Class
Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)
Public Class Temperature
Private Structure TemperatureInfo
Dim Temperature As Decimal
Dim Recorded As DateTimeOffset
End Structure
Public Event TemperatureChanged As TemperatureChanged
Private previous As Decimal
Private current As Decimal
Private tolerance As Decimal
Private tis As New List(Of TemperatureInfo)
Public Sub New(temperature As Decimal, tolerance As Decimal)
current = temperature
Dim ti As New TemperatureInfo()
ti.Temperature = temperature
ti.Recorded = DateTimeOffset.UtcNow
tis.Add(ti)
Me.tolerance = tolerance
End Sub
Public Property CurrentTemperature As Decimal
Get
Return current
End Get
Set
Dim ti As New TemperatureInfo
ti.Temperature = value
ti.Recorded = DateTimeOffset.UtcNow
previous = current
current = value
If Math.Abs(current - previous) >= tolerance Then
raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
End If
End Set
End Property
Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)
If TemperatureChangedEvent Is Nothing Then Exit Sub
Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()
For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
If d.Method.Name.Contains("Duplicate") Then
Console.WriteLine("Duplicate event handler; event handler not executed.")
Else
d.Invoke(Me, eventArgs)
End If
Next
End Sub
End Class
Public Class Example
Public WithEvents temp As Temperature
Public Shared Sub Main()
Dim ex As New Example()
End Sub
Public Sub New()
temp = New Temperature(65, 3)
RecordTemperatures()
Dim ex As New Example(temp)
ex.RecordTemperatures()
End Sub
Public Sub New(t As Temperature)
temp = t
RecordTemperatures()
End Sub
Public Sub RecordTemperatures()
temp.CurrentTemperature = 66
temp.CurrentTemperature = 63
End Sub
Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
Handles temp.TemperatureChanged
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
End Sub
Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
Handles temp.TemperatureChanged
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
End Sub
End Class
負載過重
Common Language Specification 會對多載成員施加下列規範:
成員可以根據參數數量以及任一參數的類型來重載。 呼叫慣例、傳回型別、應用於方法或其參數的自定義修飾詞,以及參數是以傳值方式還是以傳址方式傳遞,這些因素在區分多載時不會被考慮。 如需範例,請參閱命名慣例一節中有關名稱必須唯一的需求的程式碼。
只有屬性和方法可以被多載。 欄位和事件無法重載。
泛型方法可以根據泛型參數的數目來多載。
備註
op_Explicit 和 op_Implicit 運算符是傳回值不被視為多載解析方法簽章的一部分規則的例外狀況。 這兩個運算符可以根據其參數和其傳回值來多載。
例外狀況
例外狀況對象必須衍生自 System.Exception 或衍生自 System.Exception的另一種類型。 下列範例說明編譯程式錯誤,當名為 ErrorClass 的自定義類別用於例外狀況處理時,會產生此錯誤。
using System;
[assembly: CLSCompliant(true)]
public class ErrorClass
{
string msg;
public ErrorClass(string errorMessage)
{
msg = errorMessage;
}
public string Message
{
get { return msg; }
}
}
public static class StringUtilities
{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
// Compilation produces a compiler error like the following:
// Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
// System.Exception
Imports System.Runtime.CompilerServices
<Assembly: CLSCompliant(True)>
Public Class ErrorClass
Dim msg As String
Public Sub New(errorMessage As String)
msg = errorMessage
End Sub
Public ReadOnly Property Message As String
Get
Return msg
End Get
End Property
End Class
Public Module StringUtilities
<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module
' Compilation produces a compiler error like the following:
' Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
'
' Throw BadIndex
' ~~~~~~~~~~~~~~
若要更正此錯誤,ErrorClass 類別必須繼承自 System.Exception。 此外,必須重寫 Message 屬性。 下列範例會更正這些錯誤,以定義符合CLS規範的 ErrorClass 類別。
using System;
[assembly: CLSCompliant(true)]
public class ErrorClass : Exception
{
string msg;
public ErrorClass(string errorMessage)
{
msg = errorMessage;
}
public override string Message
{
get { return msg; }
}
}
public static class StringUtilities
{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
Imports System.Runtime.CompilerServices
<Assembly: CLSCompliant(True)>
Public Class ErrorClass : Inherits Exception
Dim msg As String
Public Sub New(errorMessage As String)
msg = errorMessage
End Sub
Public Overrides ReadOnly Property Message As String
Get
Return msg
End Get
End Property
End Class
Public Module StringUtilities
<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module
屬性
在 .NET 元件中,自定義屬性提供可延伸的機制,用於儲存自定義屬性,以及擷取程式設計對象的相關元數據,例如元件、類型、成員和方法參數。 自定義屬性必須衍生自 system.Attribute 或衍生自 System.Attribute的類型。
下列範例違反此規則。 它會定義不會衍生自 NumericAttribute的 System.Attribute 類別。 只有在套用不符合 CLS 規範的屬性時,編譯程式錯誤才會產生,而不是定義 類別時。
using System;
[assembly: CLSCompliant(true)]
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
public class NumericAttribute
{
private bool _isNumeric;
public NumericAttribute(bool isNumeric)
{
_isNumeric = isNumeric;
}
public bool IsNumeric
{
get { return _isNumeric; }
}
}
[Numeric(true)] public struct UDouble
{
double Value;
}
// Compilation produces a compiler error like the following:
// Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
// Attribute1.cs(7,14): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>
<AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
Public Class NumericAttribute
Private _isNumeric As Boolean
Public Sub New(isNumeric As Boolean)
_isNumeric = isNumeric
End Sub
Public ReadOnly Property IsNumeric As Boolean
Get
Return _isNumeric
End Get
End Property
End Class
<Numeric(True)> Public Structure UDouble
Dim Value As Double
End Structure
' Compilation produces a compiler error like the following:
' error BC31504: 'NumericAttribute' cannot be used as an attribute because it
' does not inherit from 'System.Attribute'.
'
' <Numeric(True)> Public Structure UDouble
' ~~~~~~~~~~~~~
符合 CLS 規範屬性的建構函式或屬性只能公開下列類型:
下列範例會定義衍生自 DescriptionAttribute的 類別。 類別建構函式具有類型為 Descriptor的參數,因此類別不符合CLS標準。 C# 編譯程式會發出警告,但編譯成功,而 Visual Basic 編譯程式不會發出警告或錯誤。
using System;
[assembly:CLSCompliantAttribute(true)]
public enum DescriptorType { type, member };
public class Descriptor
{
public DescriptorType Type;
public String Description;
}
[AttributeUsage(AttributeTargets.All)]
public class DescriptionAttribute : Attribute
{
private Descriptor desc;
public DescriptionAttribute(Descriptor d)
{
desc = d;
}
public Descriptor Descriptor
{ get { return desc; } }
}
// Attempting to compile the example displays output like the following:
// warning CS3015: 'DescriptionAttribute' has no accessible
// constructors which use only CLS-compliant types
<Assembly: CLSCompliantAttribute(True)>
Public Enum DescriptorType As Integer
Type = 0
Member = 1
End Enum
Public Class Descriptor
Public Type As DescriptorType
Public Description As String
End Class
<AttributeUsage(AttributeTargets.All)> _
Public Class DescriptionAttribute : Inherits Attribute
Private desc As Descriptor
Public Sub New(d As Descriptor)
desc = d
End Sub
Public ReadOnly Property Descriptor As Descriptor
Get
Return desc
End Get
End Property
End Class
CLSCompliantAttribute 屬性
CLSCompliantAttribute 屬性是用來指出程式專案是否符合 Common Language Specification。 CLSCompliantAttribute(Boolean) 建構函式包含單一必要參數,isCompliant,指出程式專案是否符合 CLS 標準。
在編譯階段,編譯程式會偵測假定為CLS相容的不相容元素,並發出警告。 編譯程式不會針對明確宣告為不符合規範的類型或成員發出警告。
元件開發人員可以使用 CLSCompliantAttribute 屬性,有兩種方式:
定義由符合 CLS 規範的元件所公開的公用介面元件,以及不符合 CLS 規範的元件。 當屬性用來將特定程式項目標示為符合 CLS 規範時,其使用可確保可從以 .NET 為目標的所有語言和工具存取這些專案。
為了確保元件庫的公用介面只會公開符合 CLS 規範的程序專案。 如果元素不符合 CLS 標準,編譯程式通常會發出警告。
警告
在某些情況下,不論是否使用 CLSCompliantAttribute 屬性,語言編譯程式都會強制執行符合 CLS 規範的規則。 例如,在介面中定義靜態成員違反了CLS規則。 在這方面,如果您在介面中定義 static 或 Shared(在 Visual Basic 中)成員,C# 和 Visual Basic 編譯程式都會顯示錯誤訊息,而且無法編譯應用程式。
CLSCompliantAttribute 屬性會以具有 AttributeUsageAttribute值的 AttributeTargets.All 屬性標示。 這個值可讓您將 CLSCompliantAttribute 屬性套用至任何程序專案,包括元件、模組、類型(類別、結構、列舉、介面和委派)、類型成員(建構函式、方法、屬性、字段和事件)、參數、泛型參數和傳回值。 不過,實際上,您應該只將 屬性套用至元件、類型和類型成員。 否則,每當編譯程式在連結庫的公用介面中遇到不符合規範的參數、泛型參數或傳回值時,編譯程式就會忽略 屬性並繼續產生編譯程式警告。
CLSCompliantAttribute 屬性的值是由包含的程式專案所繼承。 例如,如果元件標示為CLS相容,則其類型也符合CLS標準。 如果類型標示為符合 CLS 標準,則其巢狀類型和成員也符合 CLS 標準。
您可以將 CLSCompliantAttribute 屬性套用至受包含的程式元素,以明確覆寫繼承的合規屬性。 例如,您可以使用 CLSCompliantAttribute 屬性搭配 isCompliant 值 false,在相容的元件中定義不符合規範的類型,您可以使用 CLSCompliantAttribute 屬性搭配 true 值 ,在不相容的元件中定義符合規範的類型。 您也可以在相容的類型中定義不符合規範的成員。 然而,不符合規範的類型不能有符合規範的成員,因此您不能使用屬性,其 isCompliant 值為 true,來覆寫不符合規範類型的繼承。
當您開發元件時,應該一律使用 CLSCompliantAttribute 屬性來指出您的元件、其類型和成員是否符合 CLS 規範。
若要建立符合 CLS 規範的元件:
使用 CLSCompliantAttribute 將元件標示為符合 CLS 規範。
將元件中未符合 CLS 規範的任何公開類型標示為不符合規範。
將 CLS 相容類型中的任何公開成員標示為不符合規範。
為不符合 CLS 規範的成員提供符合 CLS 標準的替代方案。
如果您已成功標示所有不符合規範的類型和成員,則編譯程式不應該發出任何不符合規範的警告。 不過,您應該指出哪些成員不符合CLS標準,並在您的產品檔中列出其符合CLS規範的替代專案。
下列範例會使用 CLSCompliantAttribute 屬性來定義符合 CLS 規範的元件,以及具有兩個不符合 CLS 規範成員的類型 CharacterUtilities。 由於這兩個成員都會以 CLSCompliant(false) 屬性標記,因此編譯程式不會產生任何警告。 類別也為這兩種方法提供符合CLS規範的替代方案。 通常情況下,我們只是將兩個過載方法新增至 ToUTF16 方法,以提供符合 CLS 規範的選項作為替代。 不過,由於方法無法根據傳回值多載,所以符合CLS規範的方法名稱與不符合規範的方法名稱不同。
using System;
using System.Text;
[assembly:CLSCompliant(true)]
public class CharacterUtilities
{
[CLSCompliant(false)] public static ushort ToUTF16(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return Convert.ToUInt16(s[0]);
}
[CLSCompliant(false)] public static ushort ToUTF16(Char ch)
{
return Convert.ToUInt16(ch);
}
// CLS-compliant alternative for ToUTF16(String).
public static int ToUTF16CodeUnit(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return (int) Convert.ToUInt16(s[0]);
}
// CLS-compliant alternative for ToUTF16(Char).
public static int ToUTF16CodeUnit(Char ch)
{
return Convert.ToInt32(ch);
}
public bool HasMultipleRepresentations(String s)
{
String s1 = s.Normalize(NormalizationForm.FormC);
return s.Equals(s1);
}
public int GetUnicodeCodePoint(Char ch)
{
if (Char.IsSurrogate(ch))
throw new ArgumentException("ch cannot be a high or low surrogate.");
return Char.ConvertToUtf32(ch.ToString(), 0);
}
public int GetUnicodeCodePoint(Char[] chars)
{
if (chars.Length > 2)
throw new ArgumentException("The array has too many characters.");
if (chars.Length == 2) {
if (! Char.IsSurrogatePair(chars[0], chars[1]))
throw new ArgumentException("The array must contain a low and a high surrogate.");
else
return Char.ConvertToUtf32(chars[0], chars[1]);
}
else {
return Char.ConvertToUtf32(chars.ToString(), 0);
}
}
}
Imports System.Text
<Assembly: CLSCompliant(True)>
Public Class CharacterUtilities
<CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
s = s.Normalize(NormalizationForm.FormC)
Return Convert.ToUInt16(s(0))
End Function
<CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort
Return Convert.ToUInt16(ch)
End Function
' CLS-compliant alternative for ToUTF16(String).
Public Shared Function ToUTF16CodeUnit(s As String) As Integer
s = s.Normalize(NormalizationForm.FormC)
Return CInt(Convert.ToInt16(s(0)))
End Function
' CLS-compliant alternative for ToUTF16(Char).
Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
Return Convert.ToInt32(ch)
End Function
Public Function HasMultipleRepresentations(s As String) As Boolean
Dim s1 As String = s.Normalize(NormalizationForm.FormC)
Return s.Equals(s1)
End Function
Public Function GetUnicodeCodePoint(ch As Char) As Integer
If Char.IsSurrogate(ch) Then
Throw New ArgumentException("ch cannot be a high or low surrogate.")
End If
Return Char.ConvertToUtf32(ch.ToString(), 0)
End Function
Public Function GetUnicodeCodePoint(chars() As Char) As Integer
If chars.Length > 2 Then
Throw New ArgumentException("The array has too many characters.")
End If
If chars.Length = 2 Then
If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
Throw New ArgumentException("The array must contain a low and a high surrogate.")
Else
Return Char.ConvertToUtf32(chars(0), chars(1))
End If
Else
Return Char.ConvertToUtf32(chars.ToString(), 0)
End If
End Function
End Class
如果您要開發的是應用程式而非函式庫(也就是說,如果您未公開可供其他應用程式開發人員使用的類型或成員),那麼只有在您的程式語言不支援這些被應用程式使用的程式元素時,這些元素的 CLS 合規性才具備相關性。 在此情況下,當您嘗試使用不符合CLS規範的專案時,您的語言編譯程式會產生錯誤。
跨語言互作性
語言獨立性有一些可能的意義。 其中一個意義涉及從以另一種語言撰寫的應用程式順暢地取用以一種語言撰寫的類型。 第二個意義,即本文的重點,包括將以多種語言撰寫的程式代碼合併成單一 .NET 元件。
下列範例說明跨語言互作性,方法是建立名為 Utilities.dll 的類別庫,其中包含兩個類別,NumericLib 和 StringLib。
NumericLib 類別是以 C# 撰寫,而 StringLib 類別是以 Visual Basic 撰寫。 以下是 StringUtil.vb的原始程式碼,其 ToTitleCase 類別中包含單一成員 StringLib。
Imports System.Collections.Generic
Imports System.Runtime.CompilerServices
Public Module StringLib
Private exclusions As List(Of String)
Sub New()
Dim words() As String = {"a", "an", "and", "of", "the"}
exclusions = New List(Of String)
exclusions.AddRange(words)
End Sub
<Extension()> _
Public Function ToTitleCase(title As String) As String
Dim words() As String = title.Split()
Dim result As String = String.Empty
For ctr As Integer = 0 To words.Length - 1
Dim word As String = words(ctr)
If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
result += word.Substring(0, 1).ToUpper() + _
word.Substring(1).ToLower()
Else
result += word.ToLower()
End If
If ctr <= words.Length - 1 Then
result += " "
End If
Next
Return result
End Function
End Module
以下是 NumberUtil.cs 的原始程式碼,其定義具有兩個成員 NumericLib 和 IsEven的 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;
}
}
若要封裝單一元件中的兩個類別,您必須將它們編譯成模組。 若要將 Visual Basic 原始程式碼檔案編譯成模組,請使用下列命令:
vbc /t:module StringUtil.vb
如需 Visual Basic 編譯程式命令行語法的詳細資訊,請參閱從命令行建置 。
若要將 C# 原始碼檔案編譯成模組,請使用下列命令:
csc /t:module NumberUtil.cs
然後使用 連結器選項 將這兩個模組編譯成元件:
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
下列範例接著會呼叫 NumericLib.NearZero 和 StringLib.ToTitleCase 方法。 Visual Basic 程式代碼和 C# 程式代碼都能夠存取這兩個類別中的方法。
using System;
public class Example
{
public static void Main()
{
Double dbl = 0.0 - Double.Epsilon;
Console.WriteLine(NumericLib.NearZero(dbl));
string s = "war and peace";
Console.WriteLine(s.ToTitleCase());
}
}
// The example displays the following output:
// True
// War and Peace
Module Example
Public Sub Main()
Dim dbl As Double = 0.0 - Double.Epsilon
Console.WriteLine(NumericLib.NearZero(dbl))
Dim s As String = "war and peace"
Console.WriteLine(s.ToTitleCase())
End Sub
End Module
' The example displays the following output:
' True
' War and Peace
若要編譯 Visual Basic 程式代碼,請使用此命令:
vbc example.vb /r:UtilityLib.dll
若要使用 C# 編譯,請將編譯程式的名稱從 vbc 變更為 csc,並將擴展名從 .vb 變更為 .cs:
csc example.cs /r:UtilityLib.dll