Niezależność języka i składniki niezależne od języka
Platforma .NET jest niezależna od języka. Oznacza to, że jako deweloper możesz opracowywać w jednym z wielu języków przeznaczonych dla implementacji platformy .NET, takich jak C#, F# i Visual Basic. Dostęp do typów i składowych bibliotek klas opracowanych dla implementacji platformy .NET można uzyskać bez konieczności znajomości języka, w którym zostały pierwotnie napisane i bez konieczności stosowania się do jakiejkolwiek konwencji oryginalnego języka. Jeśli jesteś deweloperem składników, możesz uzyskać dostęp do składnika za pomocą dowolnej aplikacji platformy .NET, niezależnie od jego języka.
Uwaga
W pierwszej części tego artykułu omówiono tworzenie składników niezależnych od języka, czyli składników, które mogą być używane przez aplikacje napisane w dowolnym języku. Można również utworzyć pojedynczy składnik lub aplikację na podstawie kodu źródłowego napisanego w wielu językach; Zobacz Współdziałanie między językami w drugiej części tego artykułu.
Aby w pełni wchodzić w interakcje z innymi obiektami napisanymi w dowolnym języku, obiekty muszą być widoczne tylko dla obiektów wywołujących, które są wspólne dla wszystkich języków. Ten wspólny zestaw funkcji jest definiowany przez specyfikację języka wspólnego (CLS), która jest zestawem reguł, które mają zastosowanie do wygenerowanych zestawów. Specyfikacja języka wspólnego jest definiowana w partycji I, klauzule od 7 do 11 standardu ECMA-335: Infrastruktura języka wspólnego.
Jeśli składnik jest zgodny ze specyfikacją języka wspólnego, gwarantuje, że jest zgodny ze specyfikacją CLS i można uzyskać do niego dostęp z kodu w zestawach napisanych w dowolnym języku programowania, który obsługuje clS. Możesz określić, czy składnik jest zgodny ze specyfikacją języka wspólnego w czasie kompilacji, stosując atrybut CLSCompliantAttribute do kodu źródłowego. Aby uzyskać więcej informacji, zobacz atrybut CLSCompliantAttribute.
Reguły zgodności CLS
W tej sekcji omówiono reguły tworzenia składnika zgodnego ze specyfikacją CLS. Aby uzyskać pełną listę reguł, zobacz Partition I, Klauzula 11 z ECMA-335 Standard: Common Language Infrastructure.
Uwaga
Specyfikacja języka wspólnego omawia każdą regułę zgodności CLS, ponieważ dotyczy ona konsumentów (deweloperów, którzy programowo uzyskują dostęp do składnika zgodnego ze specyfikacją CLS), struktur (deweloperów, którzy używają kompilatora języka do tworzenia bibliotek zgodnych ze specyfikacją CLS) i rozszerzeń (deweloperów tworzących narzędzie, takie jak kompilator języka lub analizator kodu tworzący składniki zgodne ze specyfikacją CLS). Ten artykuł koncentruje się na regułach, które mają zastosowanie do struktur. Należy jednak pamiętać, że niektóre reguły, które mają zastosowanie do rozszerzeń, mogą również dotyczyć zestawów utworzonych przy użyciu Emocje ion. Emituj.
Aby zaprojektować składnik niezależny od języka, wystarczy zastosować reguły zgodności CLS do interfejsu publicznego składnika. Twoja prywatna implementacja nie musi być zgodna ze specyfikacją.
Ważne
Reguły zgodności CLS mają zastosowanie tylko do publicznego interfejsu składnika, a nie do jego implementacji prywatnej.
Na przykład niepodpisane liczby całkowite inne niż Byte nie są zgodne ze specyfikacją CLS. Person
Ponieważ klasa w poniższym przykładzie uwidacznia Age
właściwość typu UInt16, poniższy kod wyświetla ostrzeżenie kompilatora.
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
' ~~~
Klasę Person
CLS można zmienić, zmieniając typ Age
właściwości z UInt16 na Int16, która jest zgodna ze specyfikacją CLS, 16-bitową liczbą całkowitą ze znakiem. Nie musisz zmieniać typu pola prywatnego personAge
.
using System;
[assembly: CLSCompliant(true)]
public class Person
{
private Int16 personAge = 0;
public Int16 Age
{ get { return personAge; } }
}
<Assembly: CLSCompliant(True)>
Public Class Person
Private personAge As UInt16
Public ReadOnly Property Age As Int16
Get
Return CType(personAge, Int16)
End Get
End Property
End Class
Publiczny interfejs biblioteki składa się z następujących elementów:
Definicje klas publicznych.
Definicje publicznych składowych klas publicznych i definicje składowych dostępnych dla klas pochodnych (czyli chronionych składowych).
Parametry i zwracane typy publicznych metod klas publicznych oraz parametry i zwracane typy metod dostępnych dla klas pochodnych.
Reguły zgodności CLS są wymienione w poniższej tabeli. Tekst przepisów jest dosłowny ze standardu ECMA-335: Common Language Infrastructure, który jest prawami autorskimi 2012 przez Ecma International. Bardziej szczegółowe informacje o tych regułach można znaleźć w poniższych sekcjach.
Kategoria | Zobacz | Reguła | Numer reguły |
---|---|---|---|
Ułatwienia dostępu | Ułatwienia dostępu do składowych | Ułatwienia dostępu nie są zmieniane podczas zastępowania odziedziczonych metod, z wyjątkiem zastępowania metody dziedziczonej z innego zestawu z ułatwieniami dostępu family-or-assembly . W takim przypadku przesłonięcia mają ułatwienia dostępu family . |
10 |
Ułatwienia dostępu | Ułatwienia dostępu do składowych | Widoczność i dostępność typów i składowych jest taka, że typy podpisów każdego członka są widoczne i dostępne, gdy sam członek jest widoczny i dostępny. Na przykład publiczna metoda widoczna poza zestawem nie ma argumentu, którego typ jest widoczny tylko w zestawie. Widoczność i dostępność typów będących wystąpieniem typu ogólnego używanego w podpisie każdego elementu członkowskiego jest widoczna i dostępna, gdy sam element członkowski jest widoczny i dostępny. Na przykład wystąpienie typu ogólnego znajdującego się w podpisie elementu członkowskiego widocznego poza jego zestawem nie ma argumentu ogólnego, którego typ jest widoczny tylko w zestawie. | 12 |
Tablice | Tablice | Tablice mają elementy o typie zgodnym ze specyfikacją CLS, a wszystkie wymiary tablicy mają dolne granice zera. Jedynie fakt, że element jest tablicą, a typ elementu tablicy musi być rozróżniany między przeciążeniami. Gdy przeciążenie opiera się na co najmniej dwóch typach tablic, typy elementów są nazwane typami. | 16 |
Atrybuty | Atrybuty | Atrybuty mają typ System.Attributelub typ dziedziczący z niego. | 41 |
Atrybuty | Atrybuty | ClS zezwala tylko na podzestaw kodowań atrybutów niestandardowych. Jedynymi typami, które pojawiają się w tych kodowaniach, są (patrz Partycja IV): System.Type, , System.Int64System.ByteSystem.StringSystem.BooleanSystem.DoubleSystem.CharSystem.Int16System.Int32System.Singlei dowolny typ wyliczenia oparty na typie liczby całkowitej podstawowej zgodnej ze specyfikacją CLS. | 34 |
Atrybuty | Atrybuty | ClS nie zezwala na publicznie widoczne wymagane modyfikatory (modreq zobacz Partycja II), ale zezwala na opcjonalne modyfikatory (modopt zobacz Partycja II) nie rozumie. |
35 |
Konstruktory | Konstruktory | Konstruktor obiektu wywołuje jakiś konstruktor wystąpienia klasy bazowej, zanim wystąpi jakikolwiek dostęp do odziedziczonych danych wystąpienia. (Nie dotyczy to typów wartości, które nie muszą mieć konstruktorów). | 21 |
Konstruktory | Konstruktory | Konstruktor obiektu nie jest wywoływany z wyjątkiem części tworzenia obiektu, a obiekt nie jest inicjowany dwa razy. | 22 |
Wyliczenia | Wyliczenia | Podstawowy typ wyliczenia jest wbudowanym typem całkowitym CLS, nazwą pola jest "value__", a to pole musi być oznaczone .RTSpecialName |
7 |
Wyliczenia | Wyliczenia | Istnieją dwa różne rodzaje wyliczenia, wskazywane przez obecność lub brak atrybutu niestandardowego System.FlagsAttribute (zobacz Partition IV Library). Jeden reprezentuje nazwane wartości całkowite; druga reprezentuje nazwane flagi bitowe, które można połączyć w celu wygenerowania nienazwanej wartości. Wartość elementu enum nie jest ograniczona do określonych wartości. |
8 |
Wyliczenia | Wyliczenia | Literałowe pola statyczne wyliczenia mają typ wyliczenia. | 9 |
Zdarzenia | Wydarzenia | Metody implementujące zdarzenie są oznaczone SpecialName w metadanych. |
29 |
Zdarzenia | Wydarzenia | Dostępność wydarzenia i jego akcesoriów jest taka sama. | 30 |
Zdarzenia | Wydarzenia | Metody add i remove dla zdarzenia są obecne lub nieobecne. |
31 |
Zdarzenia | Wydarzenia | Metody add i remove dla zdarzenia przyjmują jeden parametr, którego typ definiuje typ zdarzenia i które pochodzą z Elementu System.Delegate. |
32 |
Zdarzenia | Wydarzenia | Zdarzenia są zgodne z określonym wzorcem nazewnictwa. Atrybut SpecialName, o którym mowa w regule CLS 29, jest ignorowany w odpowiednich porównaniach nazw i przestrzega reguł identyfikatorów. | 33 |
Wyjątki | Wyjątki | Obiekty, które są zgłaszane, mają typ System.Exception lub typ dziedziczący z niego. Niemniej jednak metody zgodne ze specyfikacją CLS nie są wymagane do blokowania propagacji innych typów wyjątków. | 40 |
Ogólne | Reguły zgodności CLS | Reguły CLS mają zastosowanie tylko do tych części typu, które są dostępne lub widoczne poza zestawem definiującym. | 1 |
Ogólne | Reguły zgodności CLS | Elementy członkowskie niezgodnych typów CLS nie są oznaczone jako zgodne ze specyfikacją CLS. | 2 |
Typy ogólne | Typy ogólne i elementy członkowskie | Typy zagnieżdżone mają co najmniej tyle parametrów ogólnych, jak typ otaczający. Parametry ogólne w typie zagnieżdżonym odpowiadają pozycji parametrom ogólnym w jego typie otaczającym. | 42 |
Typy ogólne | Typy ogólne i elementy członkowskie | Nazwa typu ogólnego koduje liczbę parametrów typu zadeklarowanych w typie niezagnieżdżonym lub nowo wprowadzonym do typu, jeśli jest zagnieżdżony, zgodnie z powyższymi regułami. | 43 |
Typy ogólne | Typy ogólne i elementy członkowskie | Typ ogólny powinien ponownie deklarować wystarczające ograniczenia, aby zagwarantować, że wszelkie ograniczenia typu podstawowego lub interfejsy byłyby spełnione przez ograniczenia typu ogólnego. | 44 |
Typy ogólne | Typy ogólne i elementy członkowskie | Typy używane jako ograniczenia dotyczące parametrów ogólnych są zgodne ze specyfikacją CLS. | 45 |
Typy ogólne | Typy ogólne i elementy członkowskie | Widoczność i dostępność elementów członkowskich (w tym typów zagnieżdżonych) w wystąpionym typie ogólnym należy uważać za zakres określony wystąpień, a nie ogólną deklarację typu jako całość. Przy założeniu, że nadal obowiązują reguły widoczności i ułatwień dostępu dla reguły CLS 12. | 46 |
Typy ogólne | Typy ogólne i elementy członkowskie | Dla każdej metody abstrakcyjnej lub wirtualnej ogólnej należy zaimplementować domyślną implementację betonową (nieabstraktową) | 47 |
Interfejsy | Interfejsy | Interfejsy zgodne ze specyfikacją CLS nie wymagają definicji metod niezgodnych ze specyfikacją CLS w celu ich wdrożenia. | 18 |
Interfejsy | Interfejsy | Interfejsy zgodne ze specyfikacją CLS nie definiują metod statycznych ani nie definiują pól. | 19 |
Elementy członkowskie | Ogólne składowe typu | Globalne pola statyczne i metody nie są zgodne ze specyfikacją CLS. | 36 |
Elementy członkowskie | -- | Wartość literału statycznego jest określana przy użyciu metadanych inicjowania pola. Literał zgodny ze specyfikacją CLS musi mieć wartość określoną w metadanych inicjowania pola, które są dokładnie tego samego typu co literał (lub typu bazowego, jeśli to literał jest enum ). |
13 |
Elementy członkowskie | Ogólne składowe typu | Ograniczenie vararg nie jest częścią CLS, a jedyną konwencją wywoływania obsługiwaną przez CLS jest standardowa konwencja wywoływania zarządzanego. | 15 |
Konwencje nazewnictwa | Konwencje nazewnictwa | Zestawy są zgodne z załącznikiem 7 raportu technicznego 15 standardu Unicode Standard3.0 rządzącego zestawem znaków, które mogą być uruchamiane i dołączane do identyfikatorów, dostępne w trybie online w formularzach normalizacji Unicode. Identyfikatory są w formacie kanonicznym zdefiniowanym przez formę normalizacji Unicode C. W celach CLS dwa identyfikatory są takie same, jeśli mapowania małych liter (określone przez niewrażliwe ustawienia regionalne Unicode, mapowania małych liter na jeden do jednego) są takie same. Oznacza to, że w przypadku dwóch identyfikatorów, które mają być uznawane za różne w clS, różnią się one bardziej niż tylko ich przypadkiem. Jednak w celu zastąpienia odziedziczonej definicji interfejs wiersza polecenia wymaga dokładnego kodowania oryginalnej deklaracji. | 100 |
Przeciążenie | Konwencje nazewnictwa | Wszystkie nazwy wprowadzone w zakresie zgodnym ze specyfikacją CLS są odrębne niezależnie od rodzaju, z wyjątkiem sytuacji, gdy nazwy są identyczne i rozpoznawane przez przeciążenie. Oznacza to, że chociaż usługa CTS umożliwia pojedynczemu typowi używanie tej samej nazwy dla metody i pola, clS nie. | 5 |
Przeciążenie | Konwencje nazewnictwa | Pola i typy zagnieżdżone są odrębne tylko przez porównanie identyfikatorów, mimo że usługa CTS umożliwia odróżnienie odrębnych podpisów. Metody, właściwości i zdarzenia o tej samej nazwie (według porównania identyfikatorów) różnią się od więcej niż tylko typu zwracanego, z wyjątkiem określonego w regule CLS 39 | 6 |
Przeciążenie | Overloads | Tylko właściwości i metody mogą być przeciążone. | 37 |
Przeciążenie | Overloads | Właściwości i metody mogą być przeciążone tylko na podstawie liczby i typów ich parametrów, z wyjątkiem operatorów konwersji o nazwach op_Implicit i op_Explicit , które mogą być również przeciążone na podstawie ich typu zwracanego. |
38 |
Przeciążenie | -- | Jeśli co najmniej dwie metody zgodne ze specyfikacją CLS zadeklarowane w typie mają taką samą nazwę i, dla określonego zestawu wystąpień typów, mają ten sam parametr i typy zwracane, wszystkie te metody są semantycznie równoważne w tych wystąpieniach typów. | 48 |
Właściwości | Właściwości | Metody implementujące metodę getter i metod ustawiających właściwość powinny być oznaczone SpecialName w metadanych. |
24 |
Właściwości | Właściwości | Akcesory właściwości są statyczne, wszystkie są wirtualne lub wszystkie wystąpienia. | 26 |
Właściwości | Właściwości | Typ właściwości jest typem zwrotnym gettera i typem ostatniego argumentu klasy setter. Typy parametrów właściwości są typami parametrów getter i typami wszystkich, ale ostatnim parametrem settera. Wszystkie te typy są zgodne ze specyfikacją CLS i nie są wskaźnikami zarządzanymi (oznacza to, że nie są przekazywane przez odwołanie). | 27 |
Właściwości | Właściwości | Właściwości muszą być zgodne z określonym wzorcem nazewnictwa. Atrybut SpecialName , o którym mowa w regule CLS 24, jest ignorowany w odpowiednich porównaniach nazw i jest zgodny z zasadami identyfikatorów. Właściwość ma metodę getter, metodę ustawiającą lub obie. |
28 |
Konwersja typu | Konwersja typów | W przypadku zapewnienia op_Implicit lub op_Explicit należy zapewnić alternatywne środki zapewnienia przymusu. | 39 |
Typy | Typy i podpisy składowe typu | Typy wartości skrzynkowych nie są zgodne ze specyfikacją CLS. | 3 |
Typy | Typy i podpisy składowe typu | Wszystkie typy wyświetlane w podpisie są zgodne ze specyfikacją CLS. Wszystkie typy komponowane wystąpienie typu ogólnego muszą być zgodne ze specyfikacją CLS. | 11 |
Typy | Typy i podpisy składowe typu | Odwołania wpisane nie są zgodne ze specyfikacją CLS. | 14 |
Typy | Typy i podpisy składowe typu | Niezarządzane typy wskaźników nie są zgodne ze specyfikacją CLS. | 17 |
Typy | Typy i podpisy składowe typu | Klasy zgodne ze specyfikacją CLS, typy wartości i interfejsy nie wymagają implementacji niezgodnych elementów członkowskich CLS | 20 |
Typy | Typy i podpisy składowe typu | System.Object jest zgodny ze specyfikacją CLS. Każda inna klasa zgodna ze specyfikacją CLS dziedziczy z klasy zgodnej ze specyfikacją CLS. | 23 |
Indeksowanie podsekcji:
- Typy i podpisy składowe typu
- Konwencje nazewnictwa
- Konwersja typów
- Tablice
- Interfejsy
- Wyliczenia
- Ogólne składowe typu
- Ułatwienia dostępu do składowych
- Typy ogólne i elementy członkowskie
- Konstruktory
- Właściwości
- Wydarzenia
- Overloads
- Wyjątki
- Atrybuty
Typy i podpisy składowe typu
Typ System.Object jest zgodny ze specyfikacją CLS i jest podstawowym typem wszystkich typów obiektów w systemie typów platformy .NET. Dziedziczenie na platformie .NET jest niejawne (na przykład klasa String niejawnie dziedziczy z Object
klasy) lub jawne (na przykład klasa CultureNotFoundException jawnie dziedziczy z klasy ArgumentException, która jawnie dziedziczy z klasy Exception. Aby typ pochodny był zgodny ze specyfikacją CLS, jego typ podstawowy musi być również zgodny ze specyfikacją CLS.
W poniższym przykładzie przedstawiono typ pochodny, którego typ podstawowy nie jest zgodny ze specyfikacją CLS. Definiuje klasę bazową Counter
, która używa niepodpisanej 32-bitowej liczby całkowitej jako licznika. Ponieważ klasa zapewnia funkcjonalność licznika przez zawijanie niepodpisanej liczby całkowitej, klasa jest oznaczona jako niezgodna ze specyfikacją CLS. W związku z tym klasa NonZeroCounter
pochodna , również nie jest zgodna ze specyfikacją 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
' ~~~~~~~~~~~~~~
Wszystkie typy wyświetlane w podpisach składowych, w tym typ zwracany metody lub typ właściwości, muszą być zgodne ze specyfikacją CLS. Ponadto w przypadku typów ogólnych:
Wszystkie typy tworzące wystąpienie typu ogólnego muszą być zgodne ze specyfikacją CLS.
Wszystkie typy używane jako ograniczenia dotyczące parametrów ogólnych muszą być zgodne ze specyfikacją CLS.
Wspólny system typów platformy .NET zawiera wiele wbudowanych typów, które są obsługiwane bezpośrednio przez środowisko uruchomieniowe języka wspólnego i są specjalnie kodowane w metadanych zestawu. Spośród tych typów wewnętrznych typy wymienione w poniższej tabeli są zgodne ze specyfikacją CLS.
Typ zgodny ze specyfikacją CLS | opis |
---|---|
Bajtów | 8-bitowa liczba całkowita bez znaku |
Int16 | 16-bitowa liczba całkowita ze znakiem |
Int32 | 32-bitowa liczba całkowita ze znakiem |
Int64 | 64-bitowa liczba całkowita ze znakiem |
Half | Wartość zmiennoprzecinkowa o połowie precyzji |
Pojedynczy | Wartość zmiennoprzecinkowa o pojedynczej precyzji |
Podwójne | Wartość zmiennoprzecinkowa o podwójnej precyzji |
Wartość logiczna | typ wartości true lub false |
Char | Kodowanie UTF-16 jednostki kodu |
Dziesiętne | Liczba dziesiętna nieprzecinkowa |
Intptr | Wskaźnik lub uchwyt rozmiaru zdefiniowanego przez platformę |
ciąg | Kolekcja zera, co najmniej jednego obiektu Char |
Typy wewnętrzne wymienione w poniższej tabeli nie są zgodne ze specyfikacją CLS.
Niezgodny typ | opis | Alternatywa zgodna ze specyfikacją CLS |
---|---|---|
Sbyte | 8-bitowy typ danych całkowitych ze znakiem | Int16 |
UInt16 | 16-bitowa liczba całkowita bez znaku | Int32 |
UInt32 | 32-bitowa liczba całkowita bez znaku | Int64 |
UInt64 | 64-bitowa liczba całkowita bez znaku | Int64 (może przepełnić), BigInteger lub Double |
Uintptr | Niepodpisany wskaźnik lub uchwyt | Intptr |
Biblioteka klas platformy .NET lub dowolna inna biblioteka klas może zawierać inne typy, które nie są zgodne ze specyfikacją CLS, na przykład:
Typy wartości w polu. Poniższy przykład w języku C# tworzy klasę, która ma właściwość publiczną typu
int*
o nazwieValue
. Ponieważ element jest typemint*
wartości pola, kompilator flaguje go jako niezgodne ze specyfikacją 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
Wpisane odwołania, które są specjalnymi konstrukcjami, które zawierają odwołanie do obiektu i odwołanie do typu. Wpisane odwołania są reprezentowane na platformie .NET przez klasę TypedReference .
Jeśli typ nie jest zgodny ze specyfikacją CLS, należy zastosować CLSCompliantAttribute do niego atrybut z wartością isCompliant
false
. Aby uzyskać więcej informacji, zobacz sekcję atrybutu CLSCompliantAttribute.
Poniższy przykład ilustruje problem zgodności CLS w podpisie metody i w utworzeniu wystąpienia typu ogólnego. Definiuje klasę InvoiceItem
z właściwością typu UInt32, właściwość typu Nullable<UInt32>
i konstruktor z parametrami typu UInt32 i Nullable<UInt32>
. Podczas próby skompilowania tego przykładu są wyświetlane cztery ostrzeżenia kompilatora.
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
' ~~~~~~~~~
Aby wyeliminować ostrzeżenia kompilatora, zastąp niezgodne typy CLS w interfejsie InvoiceItem
publicznym z zgodnymi typami:
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
Oprócz określonych typów wymienionych niektóre kategorie typów nie są zgodne ze specyfikacją CLS. Obejmują one niezarządzane typy wskaźników i typy wskaźników funkcji. Poniższy przykład generuje ostrzeżenie kompilatora, ponieważ używa wskaźnika do liczby całkowitej w celu utworzenia tablicy liczb całkowitych.
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
W przypadku klas abstrakcyjnych zgodnych ze specyfikacją CLS (czyli klas oznaczonych jako abstract
w języku C# lub podobnie jak MustInherit
w Visual Basic) wszystkie elementy członkowskie klasy muszą być również zgodne ze specyfikacją CLS.
Konwencje nazewnictwa
Ponieważ niektóre języki programowania są niewrażliwe na wielkość liter, identyfikatory (takie jak nazwy przestrzeni nazw, typów i elementów członkowskich) muszą się różnić w zależności od wielkości liter. Dwa identyfikatory są uważane za równoważne, jeśli ich małe mapowania są takie same. Poniższy przykład w języku C# definiuje dwie klasy Person
publiczne i person
. Ponieważ różnią się tylko wielkością liter, kompilator języka C# flaguje je jako niezgodne ze specyfikacją 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)
Identyfikatory języka programowania, takie jak nazwy przestrzeni nazw, typów i elementów członkowskich, muszą być zgodne ze standardem Unicode. To oznacza, że:
Pierwszy znak identyfikatora może być dowolną wielką literą Unicode, małą literą, literą tytułową, literą modyfikatora, inną literą lub cyfrą litery. Aby uzyskać informacje na temat kategorii znaków Unicode, zobacz System.Globalization.UnicodeCategory wyliczenie.
Kolejne znaki mogą pochodzić z dowolnej kategorii jako pierwszego znaku, a także mogą zawierać znaki inne niż odstępy, odstępy łączące znaki, liczby dziesiętne, interpunkcję łącznika i kody formatowania.
Przed porównaniem identyfikatorów należy odfiltrować kody formatowania i przekonwertować identyfikatory na formularz normalizacji Unicode C, ponieważ pojedynczy znak może być reprezentowany przez wiele jednostek kodu zakodowanych w formacie UTF-16. Sekwencje znaków, które tworzą te same jednostki kodu w postaci normalizacji Unicode C, nie są zgodne ze specyfikacją CLS. W poniższym przykładzie zdefiniowano właściwość o nazwie , która składa się z znaku ANGSTROM SIGN (U+212B) i drugiej właściwości o nazwie Å
Å
, która składa się z znaku WIELKA LITERA ALFABETU ŁACIŃSKIEGO A Z PIERŚCIENIEM POWYŻEJ (U+00C5). Kompilatory języka C# i Visual Basic flagą kodu źródłowego jako niezgodne ze specyfikacją 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
' ~
Nazwy składowe w określonym zakresie (takie jak przestrzenie nazw w zestawie, typy w przestrzeni nazw lub składowe w typie) muszą być unikatowe, z wyjątkiem nazw rozpoznawanych przez przeciążenie. To wymaganie jest bardziej rygorystyczne niż w przypadku systemu typowego, który pozwala wielu członkom w zakresie mieć identyczne nazwy, o ile są różnego rodzaju składowych (na przykład jeden jest metodą i polem). W szczególności dla składowych typu:
Pola i typy zagnieżdżone są rozróżniane samodzielnie przez nazwę.
Metody, właściwości i zdarzenia o tej samej nazwie muszą się różnić w zależności od typu zwracanego.
Poniższy przykład ilustruje wymaganie, że nazwy elementów członkowskich muszą być unikatowe w ich zakresie. Definiuje klasę o nazwie Converter
, która zawiera cztery elementy członkowskie o nazwie Conversion
. Trzy to metody, a jedna jest właściwością. Metoda zawierająca Int64 parametr jest unikatowo nazwana, ale dwie metody z parametrem Int32 nie są, ponieważ wartość zwracana nie jest traktowana jako część podpisu elementu członkowskiego. Właściwość Conversion
narusza również to wymaganie, ponieważ właściwości nie mogą mieć takiej samej nazwy jak metody przeciążone.
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
' ~~~~~~~~~~
Poszczególne języki zawierają unikatowe słowa kluczowe, więc języki docelowe środowiska uruchomieniowego języka wspólnego muszą również zapewnić pewien mechanizm odwoływania się do identyfikatorów (takich jak nazwy typów), które pokrywają się ze słowami kluczowymi. Na przykład case
jest słowem kluczowym w języku C# i Visual Basic. Jednak poniższy przykład języka Visual Basic jest w stanie uściślać klasę o nazwie case
ze słowa kluczowego case
przy użyciu nawiasów klamrowych otwierających i zamykających. W przeciwnym razie w przykładzie zostanie wygenerowany komunikat o błędzie "Słowo kluczowe jest nieprawidłowe jako identyfikator" i nie można skompilować.
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
Poniższy przykład języka C# umożliwia utworzenie wystąpienia case
klasy przy użyciu symbolu @
w celu uściślania identyfikatora ze słowa kluczowego języka. Bez niego kompilator języka C# wyświetli dwa komunikaty o błędach: "Type expected" i "Invalid expression term "case".
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
Konwersja typu
Specyfikacja języka wspólnego definiuje dwa operatory konwersji:
op_Implicit
, który jest używany do rozszerzania konwersji, które nie powodują utraty danych lub precyzji. Na przykład Decimal struktura zawiera przeciążonyop_Implicit
operator do konwertowania wartości typów całkowitych i Char wartości na Decimal wartości.op_Explicit
, który jest używany do zawężania konwersji, które mogą spowodować utratę wielkości (wartość jest konwertowana na wartość o mniejszym zakresie) lub precyzji. Na przykład Decimal struktura zawiera przeciążonyop_Explicit
operator do konwersji i wartości na Decimal i Single do konwertowania DecimalDouble wartości na wartości całkowite, Double, Singlei Char.
Jednak nie wszystkie języki obsługują przeciążenie operatora ani definicję operatorów niestandardowych. Jeśli zdecydujesz się zaimplementować te operatory konwersji, należy również udostępnić alternatywny sposób wykonania konwersji. Zalecamy podanie From
metod Xxx i To
Xxx .
W poniższym przykładzie zdefiniowano niejawne i jawne konwersje zgodne ze specyfikacją CLS. Tworzy klasę UDouble
reprezentującą niepodpisaną, podwójną precyzję, liczbę zmiennoprzecinkową. Zapewnia ona niejawne konwersje z UDouble
do Double i dla jawnych konwersji z UDouble
do Single, Double do UDouble
, i Single do UDouble
. Definiuje również metodę jako alternatywę ToDouble
dla niejawnego operatora konwersji oraz ToSingle
metod , FromDouble
i FromSingle
jako alternatywy dla jawnych operatorów konwersji.
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
Tablice
Tablice zgodne ze specyfikacją CLS są zgodne z następującymi regułami:
Wszystkie wymiary tablicy muszą mieć dolną granicę zera. W poniższym przykładzie zostanie utworzona tablica niezgodna ze specyfikacją CLS z dolną granicą. Pomimo obecności atrybutu CLSCompliantAttribute kompilator nie wykrywa, że tablica zwrócona przez
Numbers.GetTenPrimes
metodę nie jest zgodna ze specyfikacją 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
Wszystkie elementy tablicy muszą składać się z typów zgodnych ze specyfikacją CLS. W poniższym przykładzie zdefiniowano dwie metody zwracające tablice niezgodne ze specyfikacją CLS. Pierwszy zwraca tablicę UInt32 wartości. Drugi zwraca tablicę zawierającą ObjectInt32 wartości i .UInt32 Mimo że kompilator identyfikuje pierwszą tablicę jako niezgodną ze względu na jego UInt32 typ, nie rozpoznaje, że druga tablica zawiera niezgodne elementy 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() ' ~~~~~~~~~~~~
Rozpoznawanie przeciążenia dla metod, które mają parametry tablicy, opiera się na tym, że są tablicami i ich typem elementu. Z tego powodu następująca definicja przeciążonej
GetSquares
metody jest zgodna ze specyfikacją 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
Interfejsy
Interfejsy zgodne ze specyfikacją CLS mogą definiować właściwości, zdarzenia i metody wirtualne (metody bez implementacji). Interfejs zgodny ze specyfikacją CLS nie może mieć żadnego z następujących elementów:
Metody statyczne lub pola statyczne. Zarówno kompilatory języka C#, jak i Visual Basic generują błędy kompilatora, jeśli zdefiniujesz statyczny element członkowski w interfejsie.
Pola. Zarówno kompilatory języka C#, jak i Visual Basic generują błędy kompilatora, jeśli zdefiniujesz pole w interfejsie.
Metody, które nie są zgodne ze specyfikacją CLS. Na przykład poniższa definicja interfejsu zawiera metodę ,
INumber.GetUnsigned
która jest oznaczona jako niezgodna ze specyfikacją CLS. Ten przykład generuje ostrzeżenie kompilatora.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 ' ~~~~~~~~~~~
Z powodu tej reguły typy zgodne ze specyfikacją CLS nie są wymagane do implementowania niezgodnych elementów członkowskich CLS. Jeśli struktura zgodna ze specyfikacją CLS uwidacznia klasę, która implementuje niezgodny interfejs CLS, powinna również zapewnić konkretne implementacje wszystkich niezgodnych elementów członkowskich CLS.
Kompilatory języków zgodne ze specyfikacją CLS muszą również zezwalać klasie na udostępnianie oddzielnych implementacji elementów członkowskich, które mają taką samą nazwę i podpis w wielu interfejsach. Zarówno język C#, jak i Visual Basic obsługują jawne implementacje interfejsów w celu zapewnienia różnych implementacji metod o identycznych nazwach. Język Visual Basic obsługuje Implements
również słowo kluczowe, które umożliwia jawne wyznaczenie interfejsu i elementu członkowskiego implementowanych przez określony element członkowski. Poniższy przykład ilustruje ten scenariusz, definiując klasę Temperature
, która implementuje ICelsius
interfejsy i IFahrenheit
jako jawne implementacje interfejsów.
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
Wyliczenia
Wyliczenia zgodne ze specyfikacją CLS muszą być zgodne z następującymi regułami:
Podstawowym typem wyliczenia musi być wewnętrzna liczba całkowita zgodna ze specyfikacją CLS (Byte, Int16, Int32lub Int64). Na przykład poniższy kod próbuje zdefiniować wyliczenie, którego typem bazowym jest UInt32 i generuje ostrzeżenie kompilatora.
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 ' ~~~~
Typ wyliczenia musi mieć jedno pole wystąpienia o nazwie
Value__
oznaczone atrybutem FieldAttributes.RTSpecialName . Dzięki temu można odwoływać się do wartości pola niejawnie.Wyliczenie zawiera pola statyczne literału, których typy są zgodne z typem samego wyliczenia. Jeśli na przykład zdefiniujesz wyliczenie z wartościami
State.On
iState.Off
iState.Off
State.On
są polami statycznymi literałów, których typemState
jestState
.Istnieją dwa rodzaje wyliczenia:
Wyliczenie, które reprezentuje zestaw wzajemnie wykluczających się wartości o nazwie liczba całkowita. Ten typ wyliczenia jest wskazywany przez brak atrybutu niestandardowego System.FlagsAttribute .
Wyliczenie reprezentujące zestaw flag bitowych, które można połączyć w celu wygenerowania nienazwanej wartości. Ten typ wyliczenia jest wskazywany przez obecność atrybutu niestandardowego System.FlagsAttribute .
Aby uzyskać więcej informacji, zobacz dokumentację struktury Enum .
Wartość wyliczenia nie jest ograniczona do zakresu określonych wartości. Innymi słowy, zakres wartości w wyliczeniu jest zakresem jego wartości bazowej. Za pomocą Enum.IsDefined metody można określić, czy określona wartość jest elementem członkowskim wyliczenia.
Ogólne składowe typu
Specyfikacja języka wspólnego wymaga uzyskania dostępu do wszystkich pól i metod jako elementów członkowskich określonej klasy. W związku z tym globalne pola statyczne i metody (czyli pola statyczne lub metody zdefiniowane poza typem) nie są zgodne ze specyfikacją CLS. Jeśli spróbujesz dołączyć pole globalne lub metodę do kodu źródłowego, kompilatory języka C# i Visual Basic generują błąd kompilatora.
Specyfikacja języka wspólnego obsługuje tylko standardową konwencję wywoływania zarządzanego. Nie obsługuje niezarządzanych konwencji wywoływania i metod ze zmiennymi listami argumentów oznaczonymi varargs
słowem kluczowym. W przypadku list argumentów zmiennych, które są zgodne ze standardową konwencją wywoływania zarządzanego, należy użyć ParamArrayAttribute atrybutu lub implementacji poszczególnych języków, takich jak params
słowo kluczowe w języku C# i ParamArray
słowo kluczowe w Visual Basic.
Ułatwienia dostępu do składowych
Zastąpienie dziedziczonego elementu członkowskiego nie może zmienić dostępności tego elementu członkowskiego. Na przykład metoda publiczna w klasie bazowej nie może zostać zastąpiona przez metodę prywatną w klasie pochodnej. Istnieje jeden wyjątek: protected internal
element członkowski (w języku C#) lub Protected Friend
(w Visual Basic) w jednym zestawie, który jest zastępowany przez typ w innym zestawie. W takim przypadku ułatwienia dostępu przesłonięcia to Protected
.
Poniższy przykład ilustruje błąd generowany, gdy atrybut CLSCompliantAttribute jest ustawiony na true
, i Human
, który jest klasą pochodzącą z Animal
klasy , próbuje zmienić dostępność Species
właściwości z publicznej na prywatną. Przykład zostanie pomyślnie skompilowany, jeśli jego dostępność zostanie zmieniona na publiczną.
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
Typy w podpisie elementu członkowskiego muszą być dostępne za każdym razem, gdy ten element członkowski jest dostępny. Oznacza to na przykład, że publiczny element członkowski nie może zawierać parametru, którego typ jest prywatny, chroniony lub wewnętrzny. Poniższy przykład ilustruje błąd kompilatora, który wynika, gdy StringWrapper
konstruktor klasy uwidacznia wewnętrzną StringOperationType
wartość wyliczenia, która określa, jak należy opakować wartość ciągu.
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)
' ~~~~~~~~~~~~~~~~~~~
Typy ogólne i elementy członkowskie
Typy zagnieżdżone zawsze mają co najmniej tyle parametrów ogólnych, jak ich typ otaczający. Odpowiadają one pozycji do parametrów ogólnych w typie otaczającym. Typ ogólny może również zawierać nowe parametry ogólne.
Relacja między ogólnymi parametrami typu zawierającego typ i jego zagnieżdżonych typów może być ukryta przez składnię poszczególnych języków. W poniższym przykładzie typ Outer<T>
ogólny zawiera dwie zagnieżdżone klasy i Inner1A
Inner1B<U>
. Wywołania ToString
metody, którą każda klasa dziedziczy z Object.ToString()klasy , pokazują, że każda zagnieżdżona klasa zawiera parametry typu zawierającej klasę.
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]
Nazwy typów ogólnych są kodowane w nazwie formularza n, gdzie nazwa jest nazwą typu, " jest literałem znaku, a n jest liczbą parametrów zadeklarowanych dla typu lub, w przypadku zagnieżdżonych typów ogólnych, liczba nowo wprowadzonych parametrów typu. To kodowanie ogólnych nazw typów jest szczególnie interesujące dla deweloperów, którzy używają odbicia w celu uzyskania dostępu do typów ogólnych CLS-skarg w bibliotece.
Jeśli ograniczenia są stosowane do typu ogólnego, wszelkie typy używane jako ograniczenia muszą być również zgodne ze specyfikacją CLS. W poniższym przykładzie zdefiniowano klasę o nazwie BaseClass
, która nie jest zgodna ze specyfikacją CLS i klasą ogólną o nazwie BaseCollection
, której parametr typu musi pochodzić z klasy BaseClass
. Ale ponieważ BaseClass
nie jest zgodny ze specyfikacją CLS, kompilator emituje ostrzeżenie.
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)
' ~~~~~~~~~
Jeśli typ ogólny pochodzi z ogólnego typu podstawowego, musi ponownie deklarować wszelkie ograniczenia, aby zagwarantować, że ograniczenia typu podstawowego są również spełnione. W poniższym przykładzie zdefiniowano element Number<T>
, który może reprezentować dowolny typ liczbowy. Definiuje również klasę FloatingPoint<T>
reprezentującą wartość zmiennoprzecinkową. Jednak nie można skompilować kodu źródłowego, ponieważ nie stosuje ograniczenia ( Number<T>
że T musi być typem wartości) do 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)
' ~
Przykład kompiluje się pomyślnie, jeśli ograniczenie zostanie dodane do FloatingPoint<T>
klasy.
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
Specyfikacja języka wspólnego nakłada konserwatywny model tworzenia wystąpień dla typów zagnieżdżonych i chronionych elementów członkowskich. Otwarte typy ogólne nie mogą uwidaczniać pól ani elementów członkowskich z podpisami zawierającymi określone wystąpienie zagnieżdżonego, chronionego typu ogólnego. Typy inne niż ogólne, które rozszerzają określone wystąpienie ogólnej klasy bazowej lub interfejsu, nie mogą uwidaczniać pól lub składowych z podpisami, które zawierają inne wystąpienie zagnieżdżonego, chronionego typu ogólnego.
W poniższym przykładzie zdefiniowano typ C1<T>
ogólny (lub C1(Of T)
w Visual Basic) oraz klasę C1<T>.N
chronioną (lub C1(Of T).N
w Visual Basic). C1<T>
ma dwie metody i M1
M2
. Jednak nie jest zgodny ze specyfikacją CLS, M1
ponieważ próbuje zwrócić obiekt (lub C1(Of Integer).N
) z C1<T> (lub C1(Of T)
).C1<int>.N
Druga klasa, C2
, pochodzi z C1<long>
(lub C1(Of Long)
). Ma dwie metody i M3
M4
. M3
nie jest zgodny ze specyfikacją C1<int>.N
CLS, ponieważ próbuje zwrócić obiekt (lub C1(Of Integer).N
) z podklasy klasy C1<long>
. Kompilatory języka mogą być jeszcze bardziej restrykcyjne. W tym przykładzie program Visual Basic wyświetla błąd podczas próby skompilowania M4
elementu .
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)
' ~~~~~~~~~~~~~
Konstruktory
Konstruktory w klasach i strukturach zgodnych ze specyfikacją CLS muszą być zgodne z następującymi regułami:
Konstruktor klasy pochodnej musi wywołać konstruktor wystąpienia klasy bazowej, zanim uzyska dostęp do odziedziczonych danych wystąpienia. To wymaganie jest spowodowane tym, że konstruktory klas bazowych nie są dziedziczone przez ich klasy pochodne. Ta reguła nie ma zastosowania do struktur, które nie obsługują dziedziczenia bezpośredniego.
Zazwyczaj kompilatory wymuszają tę regułę niezależnie od zgodności clS, jak pokazano w poniższym przykładzie. Tworzy klasę
Doctor
, która pochodzi zPerson
klasy, aleDoctor
klasa nie może wywołaćPerson
konstruktora klasy w celu zainicjowania odziedziczonych pól wystąpienia.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() ' ~~~
Nie można wywołać konstruktora obiektu z wyjątkiem tworzenia obiektu. Ponadto nie można zainicjować obiektu dwa razy. Oznacza to na przykład, że Object.MemberwiseClone metody deserializacji nie mogą wywoływać konstruktorów.
Właściwości
Właściwości w typach zgodnych ze specyfikacją CLS muszą być zgodne z następującymi regułami:
Właściwość musi mieć właściwość setter, getter lub oba. W zestawie są one implementowane jako specjalne metody, co oznacza, że będą wyświetlane jako oddzielne metody (getter ma nazwę
get_
propertyname, a setter jestset_
właściwościname) oznaczony jakoSpecialName
w metadanych zestawu. Kompilatory języka C# i Visual Basic wymuszają tę regułę automatycznie bez konieczności stosowania atrybutu CLSCompliantAttribute .Typ właściwości jest zwracanym typem metody getter właściwości i ostatnim argumentem klasy setter. Te typy muszą być zgodne ze specyfikacją CLS, a argumenty nie mogą być przypisywane do właściwości przez odwołanie (oznacza to, że nie mogą być zarządzanymi wskaźnikami).
Jeśli właściwość ma zarówno metodę pobierania, jak i metodę ustawiającą, muszą być wirtualne, zarówno statyczne, jak i oba wystąpienia. Kompilatory języka C# i Visual Basic automatycznie wymuszają tę regułę za pomocą składni definicji właściwości.
Zdarzenia
Zdarzenie jest definiowane przez jego nazwę i jego typ. Typ zdarzenia to delegat używany do wskazywania zdarzenia. Na przykład AppDomain.AssemblyResolve zdarzenie ma typ ResolveEventHandler. Oprócz samego zdarzenia trzy metody o nazwach na podstawie nazwy zdarzenia zapewniają implementację zdarzenia i są oznaczone jako SpecialName
w metadanych zestawu:
Metoda dodawania programu obsługi zdarzeń o nazwie
add_
EventName. Na przykład metoda subskrypcji zdarzeń dla AppDomain.AssemblyResolve zdarzenia ma nazwęadd_AssemblyResolve
.Metoda usuwania programu obsługi zdarzeń o nazwie
remove_
EventName. Na przykład metoda usuwania zdarzenia AppDomain.AssemblyResolve nosi nazwęremove_AssemblyResolve
.Metoda wskazująca, że zdarzenie miało miejsce o nazwie
raise_
EventName.
Uwaga
Większość reguł specyfikacji języka wspólnego dotyczących zdarzeń jest implementowana przez kompilatory języka i są niewidoczne dla deweloperów składników.
Metody dodawania, usuwania i podnoszenia zdarzenia muszą mieć taką samą dostępność. Wszystkie muszą być również statyczne, wystąpienie lub wirtualne. Metody dodawania i usuwania zdarzenia mają jeden parametr, którego typem jest typ delegata zdarzenia. Metody dodawania i usuwania muszą być obecne lub oba te metody są nieobecne.
W poniższym przykładzie zdefiniowano klasę zgodną ze specyfikacją CLS o nazwie Temperature
, która zgłasza TemperatureChanged
zdarzenie, jeśli zmiana temperatury między dwoma odczytami jest równa lub przekracza wartość progową. Klasa Temperature
jawnie definiuje metodę raise_TemperatureChanged
, aby mogła selektywnie wykonywać programy obsługi zdarzeń.
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
Przeciążenia
Specyfikacja języka wspólnego nakłada następujące wymagania na przeciążone elementy członkowskie:
Elementy członkowskie mogą być przeciążone na podstawie liczby parametrów i typu dowolnego parametru. Wywoływanie konwencji, typu zwracanego, modyfikatorów niestandardowych zastosowanych do metody lub jej parametru oraz tego, czy parametry są przekazywane przez wartość lub przez odwołanie, nie są brane pod uwagę podczas rozróżniania między przeciążeniami. Aby zapoznać się z przykładem, zobacz kod wymagania, że nazwy muszą być unikatowe w zakresie w sekcji Konwencje nazewnictwa .
Tylko właściwości i metody mogą być przeciążone. Pola i zdarzenia nie mogą być przeciążone.
Metody ogólne mogą być przeciążone na podstawie liczby ich parametrów ogólnych.
Uwaga
Operatory op_Explicit
i op_Implicit
są wyjątkami od reguły, która zwraca wartość nie jest traktowana jako część podpisu metody do rozpoznawania przeciążenia. Te dwa operatory mogą być przeciążone na podstawie parametrów i ich wartości zwracanej.
Wyjątki
Obiekty wyjątków muszą pochodzić z innego typu pochodzącego z System.ExceptionSystem.Exceptionklasy lub innego typu. Poniższy przykład ilustruje błąd kompilatora, który powoduje, że klasa niestandardowa o nazwie ErrorClass
jest używana do obsługi wyjątków.
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
' ~~~~~~~~~~~~~~
Aby naprawić ten błąd, ErrorClass
klasa musi dziedziczyć z System.Exceptionklasy . Ponadto Message
właściwość musi zostać zastąpiona. Poniższy przykład poprawia te błędy, aby zdefiniować klasę zgodną ze specyfikacją ErrorClass
CLS.
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
Atrybuty
W zestawach platformy .NET atrybuty niestandardowe zapewniają rozszerzalny mechanizm przechowywania atrybutów niestandardowych i pobierania metadanych dotyczących obiektów programowania, takich jak zestawy, typy, elementy członkowskie i parametry metody. Atrybuty niestandardowe muszą pochodzić z elementu System.Attribute lub typu pochodzącego z System.Attribute
klasy .
Poniższy przykład narusza tę regułę. Definiuje klasę NumericAttribute
, która nie pochodzi z klasy System.Attribute. Błąd kompilatora jest wyświetlany tylko wtedy, gdy atrybut niezgodny ze specyfikacją CLS jest stosowany, a nie wtedy, gdy klasa jest zdefiniowana.
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
' ~~~~~~~~~~~~~
Konstruktor lub właściwości atrybutu zgodnego ze specyfikacją CLS mogą uwidaczniać tylko następujące typy:
W poniższym przykładzie zdefiniowano klasę pochodzącą DescriptionAttribute
z atrybutu. Konstruktor klasy ma parametr typu Descriptor
, więc klasa nie jest zgodna ze specyfikacją CLS. Kompilator języka C# emituje ostrzeżenie, ale kompiluje się pomyślnie, natomiast kompilator języka Visual Basic nie emituje ostrzeżenia ani błędu.
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
Atrybut CLSCompliantAttribute
Atrybut CLSCompliantAttribute służy do wskazania, czy element programu jest zgodny ze specyfikacją języka wspólnego. Konstruktor CLSCompliantAttribute(Boolean) zawiera jeden wymagany parametr, isCompliant, który wskazuje, czy element programu jest zgodny ze specyfikacją CLS.
W czasie kompilacji kompilator wykrywa niezgodne elementy, które są uważane za zgodne ze specyfikacją CLS i emitują ostrzeżenie. Kompilator nie emituje ostrzeżeń dla typów lub elementów członkowskich, które są jawnie zadeklarowane jako niezgodne.
Deweloperzy składników mogą używać atrybutu CLSCompliantAttribute
na dwa sposoby:
Aby zdefiniować części interfejsu publicznego uwidocznionego przez składnik zgodny ze specyfikacją CLS i części, które nie są zgodne ze specyfikacją CLS. Gdy atrybut jest używany do oznaczania określonych elementów programu jako zgodnych ze specyfikacją CLS, jego użycie gwarantuje, że te elementy są dostępne ze wszystkich języków i narzędzi przeznaczonych dla platformy .NET.
Aby upewnić się, że publiczny interfejs biblioteki składników uwidacznia tylko elementy programu zgodne ze specyfikacją CLS. Jeśli elementy nie są zgodne ze specyfikacją CLS, kompilatory zazwyczaj będą wydawać ostrzeżenie.
Ostrzeżenie
W niektórych przypadkach kompilatory języka wymuszają reguły zgodne ze specyfikacją CLS niezależnie od tego CLSCompliantAttribute
, czy atrybut jest używany. Na przykład zdefiniowanie statycznego elementu członkowskiego w interfejsie narusza regułę CLS. Jeśli w tym względzie zdefiniujesz element członkowski static
(w języku C#) lub Shared
(w języku Visual Basic) w interfejsie, kompilatory języka C# i Visual Basic wyświetlają komunikat o błędzie i nie można skompilować aplikacji.
Atrybut CLSCompliantAttribute jest oznaczony atrybutem AttributeUsageAttribute , który ma wartość AttributeTargets.All. Ta wartość umożliwia zastosowanie atrybutu CLSCompliantAttribute do dowolnego elementu programu, w tym zestawów, modułów, typów (klas, struktur, wyliczenia, interfejsów i delegatów), składowych typu (konstruktorów, metod, właściwości, pól i zdarzeń), parametrów, parametrów ogólnych i wartości zwracanych. Jednak w praktyce należy zastosować atrybut tylko do zestawów, typów i składowych typów. W przeciwnym razie kompilatory ignorują atrybut i kontynuuj generowanie ostrzeżeń kompilatora za każdym razem, gdy napotkają niezgodny parametr, parametr ogólny lub wartość zwracaną w interfejsie publicznym biblioteki.
Wartość atrybutu CLSCompliantAttribute jest dziedziczona przez zawarte elementy programu. Jeśli na przykład zestaw jest oznaczony jako zgodny ze specyfikacją CLS, jego typy są również zgodne ze specyfikacją CLS. Jeśli typ jest oznaczony jako zgodny ze specyfikacją CLS, jego zagnieżdżone typy i elementy członkowskie również są zgodne ze specyfikacją CLS.
Można jawnie zastąpić dziedziczone zgodność, stosując CLSCompliantAttribute atrybut do zawartego elementu programu. Na przykład można użyć atrybutu CLSCompliantAttribute z wartością isCompliant
false
, aby zdefiniować niezgodny typ w zgodnym zestawie i można użyć atrybutu z wartością isCompliant
true
, aby zdefiniować zgodny typ w niezgodnym zestawie. Można również zdefiniować niezgodne elementy członkowskie w zgodnym typie. Jednak niezgodny typ nie może mieć zgodnych elementów członkowskich, więc nie można użyć atrybutu z wartością isCompliant
true
, aby zastąpić dziedziczenie z niezgodnego typu.
Podczas opracowywania składników należy zawsze używać atrybutu CLSCompliantAttribute , aby wskazać, czy zestaw, jego typy i jego składowe są zgodne ze specyfikacją CLS.
Aby utworzyć składniki zgodne ze specyfikacją CLS:
Użyj polecenia , CLSCompliantAttribute aby oznaczyć zestaw jako zgodny ze specyfikacją CLS.
Oznacz wszystkie publicznie uwidocznione typy w zestawie, które nie są zgodne ze specyfikacją CLS jako niezgodne.
Oznacz wszystkie publicznie uwidocznione elementy członkowskie w typach zgodnych ze specyfikacją CLS jako niezgodne.
Podaj alternatywę zgodną ze specyfikacją CLS dla niezgodnych ze specyfikacją CLS elementów członkowskich.
Jeśli wszystkie niezgodne typy i elementy członkowskie zostały pomyślnie oznaczone, kompilator nie powinien emitować żadnych ostrzeżeń o niezgodności. Należy jednak wskazać, które elementy członkowskie nie są zgodne ze specyfikacją CLS i wyświetlić listę ich alternatyw zgodnych ze specyfikacją CLS w dokumentacji produktu.
W poniższym przykładzie użyto atrybutu CLSCompliantAttribute do zdefiniowania zestawu zgodnego ze specyfikacją CLS i typu , CharacterUtilities
który ma dwa elementy członkowskie niezgodne ze specyfikacją CLS. Ponieważ oba elementy członkowskie są oznaczone atrybutem CLSCompliant(false)
, kompilator nie generuje żadnych ostrzeżeń. Klasa zapewnia również alternatywę zgodną ze specyfikacją CLS dla obu metod. Zazwyczaj dodalibyśmy do metody dwa przeciążenia, aby zapewnić alternatywy ToUTF16
zgodne ze specyfikacją CLS. Jednak ponieważ metody nie mogą być przeciążone na podstawie wartości zwracanej, nazwy metod zgodnych ze specyfikacją CLS różnią się od nazw niezgodnych metod.
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
Jeśli tworzysz aplikację, a nie bibliotekę (czyli jeśli nie ujawniasz typów lub elementów członkowskich, które mogą być używane przez innych deweloperów aplikacji), zgodność środowiska CLS elementów programu używanych przez aplikację jest interesująca tylko wtedy, gdy język ich nie obsługuje. W takim przypadku kompilator języka wygeneruje błąd podczas próby użycia elementu niezgodnego ze specyfikacją CLS.
Współdziałanie między językami
Niezależność języka ma kilka możliwych znaczeń. Jedno znaczenie obejmuje bezproblemowe korzystanie z typów napisanych w jednym języku z aplikacji napisanej w innym języku. Drugie znaczenie, które koncentruje się na tym artykule, obejmuje połączenie kodu napisanego w wielu językach w jednym zestawie platformy .NET.
Poniższy przykład ilustruje współdziałanie między językami przez utworzenie biblioteki klas o nazwie Utilities.dll zawierającej dwie klasy NumericLib
i StringLib
. Klasa jest napisana NumericLib
w języku C#, a klasa jest napisana StringLib
w języku Visual Basic. Oto kod źródłowy elementu StringUtil.vb
, który zawiera jeden element członkowski , ToTitleCase
w swojej StringLib
klasie.
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
Oto kod źródłowy dla NumberUtil.cs, który definiuje klasę zawierającą NumericLib
dwa elementy członkowskie IsEven
i 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;
}
}
Aby spakować dwie klasy w jednym zestawie, należy je skompilować do modułów. Aby skompilować plik kodu źródłowego języka Visual Basic do modułu, użyj następującego polecenia:
vbc /t:module StringUtil.vb
Aby uzyskać więcej informacji na temat składni wiersza polecenia kompilatora Języka Visual Basic, zobacz Kompilowanie z wiersza polecenia.
Aby skompilować plik kodu źródłowego języka C# do modułu, użyj następującego polecenia:
csc /t:module NumberUtil.cs
Następnie użyj opcji konsolidatora, aby skompilować dwa moduły do zestawu:
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
Poniższy przykład wywołuje NumericLib.NearZero
metody i StringLib.ToTitleCase
. Zarówno kod Języka Visual Basic, jak i kod języka C# mogą uzyskiwać dostęp do metod w obu klasach.
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
Aby skompilować kod języka Visual Basic, użyj następującego polecenia:
vbc example.vb /r:UtilityLib.dll
Aby skompilować w języku C#, zmień nazwę kompilatora z vbc
na csc
, a następnie zmień rozszerzenie pliku z .vb na .cs:
csc example.cs /r:UtilityLib.dll