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 (modreqzobacz Partycja II), ale zezwala na opcjonalne modyfikatory (modoptzobacz 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

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 NonZeroCounterpochodna , 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 nazwie Value. Ponieważ element jest typem int* 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ą isCompliantfalse . 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ążony op_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ążony op_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 Frommetod Xxx i ToXxx .

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 ToSinglemetod , FromDoublei 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.GetUnsignedktó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 i State.Offi State.OffState.On są polami statycznymi literałów, których typem State jest State.

  • 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 Animalklasy , 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 Inner1AInner1B<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 M1M2. 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 M3M4. 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 M4elementu .

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 z Person klasy, ale Doctor 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 jako SpecialName 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.Attributeklasy .

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ą isCompliantfalse , aby zdefiniować niezgodny typ w zgodnym zestawie i można użyć atrybutu z wartością isComplianttrue , 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ą isComplianttrue , 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:

  1. Użyj polecenia , CLSCompliantAttribute aby oznaczyć zestaw jako zgodny ze specyfikacją CLS.

  2. Oznacz wszystkie publicznie uwidocznione typy w zestawie, które nie są zgodne ze specyfikacją CLS jako niezgodne.

  3. Oznacz wszystkie publicznie uwidocznione elementy członkowskie w typach zgodnych ze specyfikacją CLS jako niezgodne.

  4. 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 , CharacterUtilitiesktó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 , ToTitleCasew 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