Språkoberoende och språkoberoende komponenter

.NET är språkoberoende. Det innebär att du som utvecklare kan utveckla på ett av de många språk som är inriktade på .NET-implementeringar, till exempel C#, F# och Visual Basic. Du kan komma åt de typer och medlemmar i klassbibliotek som utvecklats för .NET-implementeringar utan att behöva känna till språket där de ursprungligen skrevs och utan att behöva följa någon av originalspråkets konventioner. Om du är komponentutvecklare kan din komponent nås av valfri .NET-app, oavsett språk.

Kommentar

Den första delen av den här artikeln beskriver hur du skapar språkoberoende komponenter, det vill: komponenter som kan användas av appar som är skrivna på valfritt språk. Du kan också skapa en enskild komponent eller app från källkod som skrivits på flera språk. se Samverkan mellan språk i den andra delen av den här artikeln.

Om du vill interagera fullt ut med andra objekt som skrivs på valfritt språk måste objekt endast exponeras för anropare som är gemensamma för alla språk. Den här gemensamma uppsättningen funktioner definieras av Common Language Specification (CLS), som är en uppsättning regler som gäller för genererade sammansättningar. Common Language Specification definieras i Partition I, Satser 7 till 11 i ECMA-335 Standard: Common Language Infrastructure.

Om din komponent överensstämmer med common language-specifikationen är den garanterad CLS-kompatibel och kan nås från kod i sammansättningar skrivna på alla programmeringsspråk som stöder CLS. Du kan avgöra om komponenten överensstämmer med common language-specifikationen vid kompileringstillfället genom att tillämpa attributet CLSCompliantAttribute på källkoden. Mer information finns i attributet CLSCompliantAttribute.

CLS-efterlevnadsregler

I det här avsnittet beskrivs reglerna för att skapa en CLS-kompatibel komponent. En fullständig lista över regler finns i Partition I, klausul 11 i ECMA-335 Standard: Common Language Infrastructure.

Kommentar

Common Language Specification diskuterar varje regel för CLS-efterlevnad eftersom den gäller för konsumenter (utvecklare som programmatiskt kommer åt en komponent som är CLS-kompatibel), ramverk (utvecklare som använder en språkkompilator för att skapa CLS-kompatibla bibliotek) och förlängare (utvecklare som skapar ett verktyg, till exempel en språkkompilator eller en kodparserare som skapar CLS-kompatibla komponenter). Den här artikeln fokuserar på reglerna när de gäller för ramverk. Observera dock att vissa av de regler som gäller för utökare även kan gälla för sammansättningar som skapas med Reflection.Emit.

Om du vill utforma en komponent som är språkoberoende behöver du bara tillämpa reglerna för CLS-efterlevnad på komponentens offentliga gränssnitt. Din privata implementering behöver inte följa specifikationen.

Viktigt!

Reglerna för CLS-efterlevnad gäller endast för en komponents offentliga gränssnitt, inte för dess privata implementering.

Till exempel är osignerade heltal som Byte inte är CLS-kompatibla. Person Eftersom klassen i följande exempel exponerar en Age egenskap av typen UInt16visar följande kod en kompilatorvarning.

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

Du kan göra Person klassen CLS-kompatibel genom att ändra typen av Age egenskapen från UInt16 till Int16, som är ett CLS-kompatibelt 16-bitars signerat heltal. Du behöver inte ändra typen av privat personAge fält.

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

Ett biblioteks offentliga gränssnitt består av följande:

  • Definitioner av offentliga klasser.

  • Definitioner av offentliga medlemmar i offentliga klasser och definitioner av medlemmar som är tillgängliga för härledda klasser (d.s. skyddade medlemmar).

  • Parametrar och returtyper av offentliga metoder för offentliga klasser samt parametrar och returtyper av metoder som är tillgängliga för härledda klasser.

Reglerna för CLS-efterlevnad visas i följande tabell. Texten i reglerna tas ordagrant från ECMA-335 Standard: Common Language Infrastructure, som är Copyright 2012 av Ecma International. Mer detaljerad information om dessa regler finns i följande avsnitt.

Kategori Gå till Regel Regelnummer
Tillgänglighet Medlemstillgänglighet Tillgängligheten får inte ändras när ärvda metoder åsidosätts, förutom när en metod som ärvs från en annan sammansättning med tillgänglighet family-or-assemblyåsidosätts. I det här fallet ska åsidosättningen ha tillgänglighet family. 10
Tillgänglighet Medlemstillgänglighet Synligheten och tillgängligheten för typer och medlemmar skall vara sådana att de typer som ingår i varje medlems underskrift ska vara synliga och tillgängliga när medlemmen själv är synlig och tillgänglig. En offentlig metod som är synlig utanför sammansättningen ska till exempel inte ha ett argument vars typ endast är synlig i sammansättningen. Synlighet och tillgänglighet för typer som utgör en instansierad generisk typ som används i signaturen för en medlem ska vara synliga och tillgängliga när medlemmen själv är synlig och tillgänglig. Till exempel ska en instansierad generisk typ som finns i signaturen för en medlem som är synlig utanför dess sammansättning inte ha ett generiskt argument vars typ endast är synlig inom sammansättningen. 12
Matriser Matriser Matriser ska ha element med en CLS-kompatibel typ och alla dimensioner i matrisen ska ha lägre gränser på noll. Endast det faktum att ett objekt är en matris och elementtypen för matrisen ska krävas för att skilja mellan överlagringar. När överlagring baseras på två eller flera matristyper ska elementtyperna namnges. 16
Attribut Attribut Attributen ska vara av typen System.Attribute, eller en typ som ärver från den. 41
Attribut Attribut CLS tillåter endast en delmängd av kodningarna för anpassade attribut. De enda typerna som ska visas i dessa kodningar är (se Partition IV): System.Type, System.String, System.Char, System.Boolean, System.Byte, System.Int16, System.Int32, System.Int64, , System.Single, och System.Doublealla uppräkningstyper baserat på en CLS-kompatibel bas heltalstyp. 34
Attribut Attribut CLS tillåter inte offentligt synliga obligatoriska modifierare (modreqse Partition II), men tillåter valfria modifierare (modoptse Partition II) som det inte förstår. 35
Konstruktorer Konstruktörer En objektkonstruktor ska anropa någon instanskonstruktor för sin basklass innan någon åtkomst sker till ärvda instansdata. (Detta gäller inte för värdetyper som inte behöver ha konstruktorer.) 21
Konstruktorer Konstruktörer En objektkonstruktor får inte anropas förutom som en del av skapandet av ett objekt, och ett objekt får inte initieras två gånger. 22
Uppräkningar Uppräkningar Den underliggande typen av en uppräkning ska vara en inbyggd CLS-heltalstyp, fältets namn ska vara "value__" och fältet ska vara märkt RTSpecialName. 7
Uppräkningar Uppräkningar Det finns två distinkta typer av uppräkningar, vilket indikeras av förekomsten eller frånvaron av det System.FlagsAttribute anpassade attributet (se Partition IV-bibliotek). En representerar namngivna heltalsvärden; den andra representerar namngivna bitflaggor som kan kombineras för att generera ett namnlöst värde. Värdet för en enum är inte begränsat till de angivna värdena. 8
Uppräkningar Uppräkningar Literala statiska fält i ett uppräkning ska ha själva uppräkningens typ. 9
Händelser Händelser De metoder som implementerar en händelse ska markeras SpecialName i metadata. 29
Händelser Händelser Tillgängligheten för en händelse och dess accessorer ska vara identiska. 30
Händelser Händelser Metoderna add och remove för en händelse ska antingen vara närvarande eller frånvarande. 31
Händelser Händelser Metoderna add och remove för en händelse ska var och en ta en parameter vars typ definierar händelsens typ och som ska härledas från System.Delegate. 32
Händelser Händelser Händelser ska följa ett specifikt namngivningsmönster. Det SpecialName-attribut som avses i CLS-regel 29 ska ignoreras i lämpliga namnjämförelser och ska följa reglerna för identifierare. 33
Undantag Undantag Objekt som utlöses ska vara av typen System.Exception eller en typ som ärver från den. CLS-kompatibla metoder krävs dock inte för att blockera spridning av andra typer av undantag. 40
Allmänt CLS-efterlevnadsregler CLS-regler gäller endast för de delar av en typ som är tillgängliga eller synliga utanför den definierande sammansättningen. 1
Allmänt CLS-efterlevnadsregler Medlemmar av icke-CLS-kompatibla typer får inte markeras som CLS-kompatibla. 2
Generika Allmänna typer och medlemmar Kapslade typer ska ha minst lika många generiska parametrar som den omslutande typen. Generiska parametrar i en kapslad typ motsvarar de generiska parametrarna i sin omslutande typ efter position. 42
Generika Allmänna typer och medlemmar Namnet på en generisk typ ska koda antalet typparametrar som deklarerats för den icke-kapslade typen, eller som nyligen införts för typen om den är kapslad, enligt de regler som definieras ovan. 43
Generika Allmänna typer och medlemmar En generisk typ ska innehålla tillräckliga begränsningar för att garantera att eventuella begränsningar för bastypen eller gränssnitten skulle uppfyllas av de allmänna typbegränsningarna. 44
Generika Allmänna typer och medlemmar Typer som används som begränsningar för generiska parametrar ska själva vara CLS-kompatibla. 45
Generika Allmänna typer och medlemmar Medlemmarnas synlighet och tillgänglighet (inklusive kapslade typer) i en instansierad generisk typ ska anses vara begränsad till den specifika instansieringen snarare än den generiska typdeklarationen som helhet. Om vi antar detta gäller fortfarande synlighets- och tillgänglighetsreglerna i CLS-regel 12. 46
Generika Allmänna typer och medlemmar För varje abstrakt eller virtuell generisk metod ska det finnas en standardimplementering av betong (nonabstract) 47
Gränssnitt Gränssnitt CLS-kompatibla gränssnitt ska inte kräva definitionen av icke-CLS-kompatibla metoder för att implementera dem. 18
Gränssnitt Gränssnitt CLS-kompatibla gränssnitt får inte definiera statiska metoder och får inte heller definiera fält. 19
Medlemmar Skriv medlemmar i allmänhet Globala statiska fält och metoder är inte CLS-kompatibla. 36
Medlemmar -- Värdet för en literal statisk anges med hjälp av fältinitieringsmetadata. En CLS-kompatibel literal måste ha ett värde som anges i fältinitieringsmetadata som är av exakt samma typ som literalen (eller av den underliggande typen, om den literalen är en enum). 13
Medlemmar Skriv medlemmar i allmänhet Vararg-begränsningen är inte en del av CLS, och den enda anropande konventionen som stöds av CLS är standardkonventionen för hanterade anrop. 15
Namngivningskonventioner Namngivningskonventioner Sammansättningar skall följa bilaga 7 i den tekniska rapporten 15 i Unicode Standard3.0 som reglerar de teckenuppsättningar som tillåts att startas och tas med i identifierare, tillgängliga online på Unicode Normalization Forms. Identifierare ska vara i kanoniskt format som definieras av Unicode Normalization Form C. För CLS-ändamål är två identifierare samma om deras gemener mappningar (enligt unicode-språkvariantens okänsliga, en-till-en-gemena mappningar) är desamma. Det innebär att för att två identifierare ska betraktas som olika enligt CLS ska de skilja sig mer än bara deras fall. Men för att åsidosätta en ärvd definition kräver CLI att exakt kodning av den ursprungliga deklarationen används. 4
Överbelastning Namngivningskonventioner Alla namn som introduceras i ett CLS-kompatibelt omfång ska vara distinkta oberoende av slag, utom när namnen är identiska och lösta via överlagring. Det innebär att även om CTS tillåter att en enskild typ använder samma namn för en metod och ett fält, gör INTE CLS det. 5
Överbelastning Namngivningskonventioner Fält och kapslade typer ska vara distinkta enbart efter identifierarjämförelse, även om CTS gör det möjligt att särskilja distinkta signaturer. Metoder, egenskaper och händelser som har samma namn (efter identifierarjämförelse) ska skilja sig med mer än bara returtypen, förutom enligt vad som anges i CLS-regel 39 6
Överbelastning Överbelastning Endast egenskaper och metoder kan överbelastas. 37
Överbelastning Överbelastning Egenskaper och metoder kan endast överbelastas baserat på antalet och typerna av deras parametrar, förutom konverteringsoperatorerna med namnet op_Implicit och op_Explicit, som också kan överbelastas baserat på deras returtyp. 38
Överbelastning -- Om två eller flera CLS-kompatibla metoder som deklareras i en typ har samma namn och för en specifik uppsättning instansieringar av typen har de samma parameter- och returtyper, ska alla dessa metoder vara semantiskt likvärdiga vid dessa typinstansieringar. 48
Egenskaper Egenskaper De metoder som implementerar getter- och settermetoderna för en egenskap ska markeras SpecialName i metadata. 24
Egenskaper Egenskaper En egenskaps accessorer ska alla vara statiska, alla vara virtuella eller alla vara instanser. 26
Egenskaper Egenskaper Typen av en egenskap ska vara avkastningstypen för gettern och typen av det sista argumentet för setter. Egenskapsparametrarnas typer ska vara parametrarnas typer till gettern och typerna av alla utom den slutliga parametern för settern. Alla dessa typer ska vara CLS-kompatibla och ska inte vara hanterade pekare (det vill: får inte skickas med referens). 27
Egenskaper Egenskaper Egenskaperna ska följa ett specifikt namngivningsmönster. Det SpecialName attribut som avses i CLS-regel 24 ska ignoreras i lämpliga namnjämförelser och ska följa reglerna för identifierare. En egenskap ska ha en gettermetod, en setter-metod eller båda. 28
Typkonvertering Typkonvertering Om antingen op_Implicit eller op_Explicit tillhandahålls, skall ett alternativt sätt att tillhandahålla tvånget tillhandahållas. 39
Typer Typer och typ av medlemssignaturer Boxade värdetyper är inte CLS-kompatibla. 3
Typer Typer och typ av medlemssignaturer Alla typer som förekommer i en signatur ska vara CLS-kompatibla. Alla typer som utgör en instansierad generisk typ ska vara CLS-kompatibla. 11
Typer Typer och typ av medlemssignaturer Inskrivna referenser är inte CLS-kompatibla. 14
Typer Typer och typ av medlemssignaturer Ohanterade pekartyper är inte CLS-kompatibla. 17
Typer Typer och typ av medlemssignaturer CLS-kompatibla klasser, värdetyper och gränssnitt ska inte kräva implementering av icke-CLS-kompatibla medlemmar 20
Typer Typer och typ av medlemssignaturer System.Object är CLS-kompatibelt. Alla andra CLS-kompatibla klasser ska ärva från en CLS-kompatibel klass. 23

Indexera till underavsnitt:

Typer och typmedlemssignaturer

Typen System.Object är CLS-kompatibel och är bastypen för alla objekttyper i .NET-typsystemet. Arv i .NET är antingen implicit (till exempel strängklassenärver implicit från Object klassen) eller explicit (till exempel ärver klassen CultureNotFoundException explicit från klassen ArgumentException, som uttryckligen ärver från klassen Exception. För att en härledd typ ska vara CLS-kompatibel måste dess bastyp också vara CLS-kompatibel.

I följande exempel visas en härledd typ vars bastyp inte är CLS-kompatibel. Den definierar en basklass Counter som använder ett osignerat 32-bitars heltal som en räknare. Eftersom klassen tillhandahåller räknarfunktioner genom att omsluta ett osignerat heltal markeras klassen som icke-CLS-kompatibel. Därför är en härledd klass, NonZeroCounter, inte heller CLS-kompatibel.

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

Alla typer som visas i medlemssignaturer, inklusive en metods returtyp eller en egenskapstyp, måste vara CLS-kompatibla. Dessutom för generiska typer:

  • Alla typer som utgör en instansierad allmän typ måste vara CLS-kompatibla.

  • Alla typer som används som begränsningar för generiska parametrar måste vara CLS-kompatibla.

Det vanliga .NET-typsystemet innehåller många inbyggda typer som stöds direkt av den gemensamma språkkörningen och som är särskilt kodade i en sammansättnings metadata. Av dessa inbyggda typer är de typer som anges i följande tabell CLS-kompatibla.

CLS-kompatibel typ beskrivning
Byte 8-bitars osignerat heltal
Int16 16-bitars signerat heltal
Int32 32-bitars signerat heltal
Int64 64-bitars signerat heltal
Halv Flyttalsvärde med halv precision
Enda Flyttal med enkel precision
Dubbel Flyttalsvärde med dubbel precision
Boolesk värdetyp för sant eller falskt
Char UTF-16-kodad kodenhet
Decimal Decimaltal som inte är flyttal
IntPtr Pekare eller handtag med en plattformsdefinierad storlek
Sträng Samling med noll, ett eller flera Teckenobjekt

De inbyggda typer som anges i följande tabell är inte CLS-kompatibla.

Inkompatibel typ beskrivning Alternativ som uppfyller CLS
SByte 8-bitars signerad heltalsdatatyp Int16
UInt16 16-bitars osignerat heltal Int32
UInt32 32-bitars osignerat heltal Int64
UInt64 64-bitars osignerat heltal Int64 (kan spilla över), BigInteger eller Double
UIntPtr Osignerad pekare eller handtag IntPtr

.NET-klassbiblioteket eller något annat klassbibliotek kan innehålla andra typer som inte är CLS-kompatibla, till exempel:

  • Boxade värdetyper. I följande C#-exempel skapas en klass som har en offentlig egenskap av typen int* med namnet Value. Eftersom en int* är en boxad värdetyp flaggar kompilatorn den som icke-CLS-kompatibel.

    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
    
  • Inskrivna referenser, som är specialkonstruktioner som innehåller en referens till ett objekt och en referens till en typ. Inskrivna referenser representeras i .NET av TypedReference klassen.

Om en typ inte är CLS-kompatibel bör du använda CLSCompliantAttribute attributet med värdet isCompliantfalse för det. Mer information finns i avsnittet CLSCompliantAttribute-attribut .

I följande exempel visas problemet med CLS-efterlevnad i en metodsignatur och i instansiering av generisk typ. Den definierar en InvoiceItem klass med en egenskap av typen UInt32, en egenskap av typen Nullable<UInt32>och en konstruktor med parametrar av typen UInt32 och Nullable<UInt32>. Du får fyra kompilatorvarningar när du försöker kompilera det här exemplet.

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

Om du vill eliminera kompilatorvarningarna ersätter du de icke-CLS-kompatibla typerna i det InvoiceItem offentliga gränssnittet med kompatibla typer:

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

Förutom de specifika typer som anges är vissa typer av typer inte CLS-kompatibla. Dessa omfattar ohanterade pekartyper och funktionspekartyper. I följande exempel genereras en kompilatorvarning eftersom den använder en pekare till ett heltal för att skapa en matris med heltal.

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

För CLS-kompatibla abstrakta klasser (d.v.s. klasser som markerats som abstract i C# eller som MustInherit i Visual Basic) måste alla medlemmar i klassen också vara CLS-kompatibla.

Namngivningskonventioner

Eftersom vissa programmeringsspråk är skiftlägeskänsliga måste identifierare (till exempel namnområden, typer och medlemmar) skilja sig mer än så. Två identifierare anses vara likvärdiga om deras gemener mappningar är desamma. I följande C#-exempel definieras två offentliga klasser och Personperson. Eftersom de bara skiljer sig åt från fall flaggar C#-kompilatorn dem som inte CLS-kompatibla.

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)

Programmeringsspråkidentifierare, till exempel namnrymder, typer och medlemmar, måste överensstämma med Unicode Standard. Detta innebär att:

  • Det första tecknet i en identifierare kan vara valfri Unicode-versal, gemen bokstav, rubrikfallsbokstav, modifierarbokstav, annan bokstav eller bokstavsnummer. Information om Unicode-teckenkategorier finns i System.Globalization.UnicodeCategory uppräkningen.

  • Efterföljande tecken kan komma från någon av kategorierna som det första tecknet och kan även innehålla tecken som inte är avståndstecken, avstånd som kombinerar markeringar, decimaltal, anslutningspunktion och formateringskoder.

Innan du jämför identifierare bör du filtrera bort formateringskoder och konvertera identifierarna till Unicode Normalization Form C, eftersom ett enda tecken kan representeras av flera UTF-16-kodade kodenheter. Teckensekvenser som producerar samma kodenheter i Unicode Normalization Form C är inte CLS-kompatibla. I följande exempel definieras en egenskap med namnet , som består av tecknet ANGSTROM SIGN (U+212B) och en andra egenskap med namnet Å, som består av tecknet LATINSK VERSAL BOKSTAV A MED RING OVAN (U+00C5). Både C#- och Visual Basic-kompilatorerna flaggar källkoden som icke-CLS-kompatibel.

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

Medlemsnamn inom ett visst omfång (till exempel namnrymderna i en sammansättning, typerna i ett namnområde eller medlemmarna inom en typ) måste vara unika förutom namn som löses genom överlagring. Det här kravet är strängare än för det gemensamma typsystemet, vilket gör att flera medlemmar inom ett omfång kan ha identiska namn så länge de är olika typer av medlemmar (till exempel en är en metod och en är ett fält). I synnerhet för typmedlemmar:

  • Fält och kapslade typer särskiljs enbart med namn.

  • Metoder, egenskaper och händelser som har samma namn måste skilja sig åt med mer än bara returtyp.

I följande exempel visas kravet på att medlemsnamn måste vara unika inom deras omfång. Den definierar en klass med namnet Converter som innehåller fyra medlemmar med namnet Conversion. Tre är metoder och en är en egenskap. Metoden som innehåller en Int64 parameter är unikt namngiven, men de två metoderna med en Int32 parameter är inte det, eftersom returvärdet inte betraktas som en del av en medlems signatur. Egenskapen Conversion bryter också mot det här kravet eftersom egenskaper inte kan ha samma namn som överlagrade metoder.

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

Enskilda språk innehåller unika nyckelord, så språk som riktar sig mot den gemensamma språkkörningen måste också ge en mekanism för att referera till identifierare (till exempel typnamn) som sammanfaller med nyckelord. Är till exempel case ett nyckelord i både C# och Visual Basic. Följande Visual Basic-exempel kan dock skilja en klass med namnet case från nyckelordet case med hjälp av inledande och avslutande klammerparenteser. Annars skulle exemplet generera felmeddelandet "Nyckelordet är inte giltigt som identifierare" och kompileras inte.

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

Följande C#-exempel kan instansiera case klassen med hjälp av symbolen @ för att skilja identifieraren från språknyckelordet. Utan den skulle C#-kompilatorn visa två felmeddelanden, "Type expected" och "Invalid expression term 'case'."

using System;

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

Typkonvertering

Common Language Specification definierar två konverteringsoperatorer:

  • op_Implicit, som används för att bredda konverteringar som inte resulterar i förlust av data eller precision. Strukturen innehåller till exempel Decimal en överbelastad op_Implicit operator för att konvertera värden för integraltyper och Char värden till Decimal värden.

  • op_Explicit, som används för att begränsa konverteringar som kan leda till förlust av storlek (ett värde konverteras till ett värde som har ett mindre intervall) eller precision. Strukturen innehåller till exempel Decimal en överbelastad op_Explicit operator som konverterar Double och Single värden till Decimal och för att konvertera Decimal värden till integralvärden, Double, Singleoch Char.

Alla språk stöder dock inte överlagring av operatorer eller definitionen av anpassade operatorer. Om du väljer att implementera dessa konverteringsoperatorer bör du också ange ett alternativt sätt att utföra konverteringen. Vi rekommenderar att du anger Fromxxx - och Toxxx-metoder .

I följande exempel definieras implicita och explicita CLS-kompatibla konverteringar. Den skapar en UDouble klass som representerar ett osignerat flyttalsnummer med dubbel precision. Den tillhandahåller implicita konverteringar från UDouble till Double och för explicita konverteringar från UDouble till Single, Double till UDoubleoch Single till UDouble. Den definierar också en ToDouble metod som ett alternativ till den implicita konverteringsoperatorn och ToSinglemetoderna , FromDoubleoch FromSingle som alternativ till de explicita konverteringsoperatorerna.

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

Matriser

CLS-kompatibla matriser följer följande regler:

  • Alla dimensioner i en matris måste ha en lägre gräns på noll. I följande exempel skapas en icke-CLS-kompatibel matris med en lägre gräns på en. Trots förekomsten av CLSCompliantAttribute attributet identifierar kompilatorn inte att matrisen som returneras av Numbers.GetTenPrimes metoden inte är CLS-kompatibel.

    [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
    
  • Alla matriselement måste bestå av CLS-kompatibla typer. I följande exempel definieras två metoder som returnerar icke-CLS-kompatibla matriser. Den första returnerar en matris med UInt32 värden. Den andra returnerar en Object matris som innehåller Int32 och UInt32 värden. Även om kompilatorn identifierar den första matrisen som icke-kompatibel på grund av sin UInt32 typ, kan den inte identifiera att den andra matrisen innehåller icke-CLS-kompatibla element.

    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()
    '                              ~~~~~~~~~~~~
    
  • Överlagringsmatchning för metoder som har matrisparametrar baseras på det faktum att de är matriser och deras elementtyp. Därför är följande definition av en överlagrad GetSquares metod CLS-kompatibel.

    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
    

Gränssnitt

CLS-kompatibla gränssnitt kan definiera egenskaper, händelser och virtuella metoder (metoder utan implementering). Ett CLS-kompatibelt gränssnitt kan inte ha något av följande:

  • Statiska metoder eller statiska fält. Både C#- och Visual Basic-kompilatorerna genererar kompilatorfel om du definierar en statisk medlem i ett gränssnitt.

  • Fält. Både C# och Visual Basic-kompilatorerna genererar kompilatorfel om du definierar ett fält i ett gränssnitt.

  • Metoder som inte är CLS-kompatibla. Följande gränssnittsdefinition innehåller till exempel en metod, INumber.GetUnsigned, som är markerad som icke-CLS-kompatibel. Det här exemplet genererar en kompilatorvarning.

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

    På grund av den här regeln krävs inte CLS-kompatibla typer för att implementera icke-CLS-kompatibla medlemmar. Om ett CLS-kompatibelt ramverk exponerar en klass som implementerar ett icke-CLS-kompatibelt gränssnitt bör det även tillhandahålla konkreta implementeringar av alla icke-CLS-kompatibla medlemmar.

CLS-kompatibla språkkompilatorer måste också tillåta en klass att tillhandahålla separata implementeringar av medlemmar som har samma namn och signatur i flera gränssnitt. Både C# och Visual Basic stöder explicita gränssnittsimplementeringar för att tillhandahålla olika implementeringar av identiskt namngivna metoder. Visual Basic stöder också nyckelordet Implements , som gör att du uttryckligen kan ange vilket gränssnitt och vilken medlem en viss medlem implementerar. I följande exempel visas det här scenariot genom att definiera en Temperature klass som implementerar gränssnitten ICelsius och IFahrenheit som explicita gränssnittsimplementeringar.

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

Uppräkningar

CLS-kompatibla uppräkningar måste följa dessa regler:

  • Den underliggande typen av uppräkning måste vara ett inbyggt CLS-kompatibelt heltal (Byte, , Int16Int32eller Int64). Följande kod försöker till exempel definiera en uppräkning vars underliggande typ är UInt32 och genererar en kompilatorvarning.

    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
    '                ~~~~
    
  • En uppräkningstyp måste ha ett enda instansfält med namnet Value__ som är markerat med attributet FieldAttributes.RTSpecialName . På så sätt kan du referera till fältvärdet implicit.

  • En uppräkning innehåller literala statiska fält vars typer matchar själva uppräkningstypen. Om du till exempel definierar en State uppräkning med värden för State.On och State.Off, State.On och State.Off båda är literala statiska fält vars typ är State.

  • Det finns två typer av uppräkningar:

    • En uppräkning som representerar en uppsättning ömsesidigt uteslutande, namngivna heltalsvärden. Den här typen av uppräkning indikeras av avsaknaden av det System.FlagsAttribute anpassade attributet.

    • En uppräkning som representerar en uppsättning bitflaggor som kan kombineras för att generera ett namnlöst värde. Den här typen av uppräkning indikeras av förekomsten av det System.FlagsAttribute anpassade attributet.

    Mer information finns i dokumentationen Enum för strukturen.

  • Värdet för en uppräkning är inte begränsat till intervallet för dess angivna värden. Med andra ord är intervallet med värden i en uppräkning intervallet för dess underliggande värde. Du kan använda Enum.IsDefined metoden för att avgöra om ett angivet värde är medlem i en uppräkning.

Skriv medlemmar i allmänhet

Common Language Specification kräver att alla fält och metoder används som medlemmar i en viss klass. Därför är globala statiska fält och metoder (dvs. statiska fält eller metoder som definieras förutom en typ) inte CLS-kompatibla. Om du försöker inkludera ett globalt fält eller en metod i källkoden genererar både C#- och Visual Basic-kompilatorerna ett kompilatorfel.

Common Language Specification stöder endast standardkonventionen för hanterade anrop. Den stöder inte ohanterade anropskonventioner och metoder med variabelargumentlistor markerade med nyckelordet varargs . För variabelargumentlistor som är kompatibla med standardkonventionen för hanterade anrop använder du ParamArrayAttribute attributet eller det enskilda språkets implementering, till exempel nyckelordet params i C# och nyckelordet ParamArray i Visual Basic.

Medlemstillgänglighet

Att åsidosätta en ärvd medlem kan inte ändra tillgängligheten för den medlemmen. En offentlig metod i en basklass kan till exempel inte åsidosättas av en privat metod i en härledd klass. Det finns ett undantag: en protected internal (i C#) eller Protected Friend (i Visual Basic) medlem i en sammansättning som åsidosätts av en typ i en annan sammansättning. I så fall är Protectedåsidosättningens tillgänglighet .

I följande exempel visas det fel som genereras när attributet CLSCompliantAttribute är inställt på true, och Human, som är en klass som härleds från Animal, försöker ändra egenskapens Species tillgänglighet från offentlig till privat. Exemplet kompileras om dess tillgänglighet ändras till offentlig.

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

Typer i signaturen för en medlem måste vara tillgängliga när medlemmen är tillgänglig. Det innebär till exempel att en offentlig medlem inte kan inkludera en parameter vars typ är privat, skyddad eller intern. I följande exempel visas kompilatorfelet som resulterar när en StringWrapper klasskonstruktor exponerar ett internt StringOperationType uppräkningsvärde som avgör hur ett strängvärde ska omslutas.

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

Allmänna typer och medlemmar

Kapslade typer har alltid minst lika många generiska parametrar som deras omslutningstyp. Dessa motsvarar de generiska parametrarna i omslutningstypen efter position. Den generiska typen kan också innehålla nya generiska parametrar.

Relationen mellan de generiska typparametrarna för en innehållande typ och dess kapslade typer kan döljas med syntaxen för enskilda språk. I följande exempel innehåller en allmän typ Outer<T> två kapslade klasser och Inner1AInner1B<U>. Anropen ToString till metoden, som varje klass ärver från Object.ToString(), visar att varje kapslad klass innehåller typparametrarna för dess innehållande klass.

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]

Generiska typnamn kodas i formulärnamnet'n, där namnet är typnamnet, är en teckenliteral och n är antalet parametrar som deklarerats för typen, eller, för kapslade generiska typer, antalet nyligen introducerade typparametrar. Den här kodningen av generiska typnamn är främst av intresse för utvecklare som använder reflektion för att få åtkomst till ALLMÄNNA CLS-klagomålstyper i ett bibliotek.

Om begränsningar tillämpas på en allmän typ måste alla typer som används som begränsningar också vara CLS-kompatibla. I följande exempel definieras en klass med namnet BaseClass som inte är CLS-kompatibel och en generisk klass med namnet BaseCollection vars typparameter måste härledas från BaseClass. Men eftersom BaseClass det inte är CLS-kompatibelt genererar kompilatorn en varning.

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

Om en allmän typ härleds från en allmän bastyp måste den omdeklareras med eventuella begränsningar så att den kan garantera att begränsningarna för bastypen också uppfylls. I följande exempel definieras en Number<T> som kan representera vilken numerisk typ som helst. Den definierar också en FloatingPoint<T> klass som representerar ett flyttalsvärde. Källkoden kan dock inte kompileras eftersom den inte tillämpar villkoret på Number<T> (att T måste vara en värdetyp) för 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)
'                                                          ~

Exemplet kompileras om villkoret läggs till i FloatingPoint<T> klassen.

using System;

[assembly:CLSCompliant(true)]

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

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

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

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

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

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

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

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

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

Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)
    Public Sub New(number As T)
        MyBase.New(number)
        If TypeOf number Is Single Or
                 TypeOf number Is Double Or
                 TypeOf number Is Decimal Then
            Me.number = Convert.ToDouble(number)
        Else
            throw new ArgumentException("The number parameter is not a floating-point number.")
        End If
    End Sub
End Class

Common Language Specification tillämpar en konservativ modell per instansiering för kapslade typer och skyddade medlemmar. Öppna generiska typer kan inte exponera fält eller medlemmar med signaturer som innehåller en specifik instansiering av en kapslad, skyddad allmän typ. Icke-generiska typer som utökar en specifik instansiering av en allmän basklass eller ett gränssnitt kan inte exponera fält eller medlemmar med signaturer som innehåller en annan instansiering av en kapslad, skyddad allmän typ.

I följande exempel definieras en allmän typ ( C1<T> eller C1(Of T) i Visual Basic) och en skyddad klass C1<T>.N (eller C1(Of T).N i Visual Basic). C1<T> har två metoder och M1M2. Är dock M1 inte CLS-kompatibelt eftersom det försöker returnera ett C1<int>.N (eller C1(Of Integer).N) objekt från C1<T> (eller C1(Of T)). En andra klass, C2, härleds från C1<long> (eller C1(Of Long)). Den har två metoder och M3M4. M3 är inte CLS-kompatibel eftersom det försöker returnera ett C1<int>.N (eller C1(Of Integer).N) objekt från en underklass av C1<long>. Språkkompilatorer kan vara ännu mer restriktiva. I det här exemplet visar Visual Basic ett fel när det försöker kompilera M4.

using System;

[assembly:CLSCompliant(true)]

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

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

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

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

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

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


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

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

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

Konstruktorer

Konstruktorer i CLS-kompatibla klasser och strukturer måste följa dessa regler:

  • En konstruktor för en härledd klass måste anropa instanskonstruktorn för sin basklass innan den får åtkomst till ärvda instansdata. Det här kravet beror på att basklasskonstruktorer inte ärvs av sina härledda klasser. Den här regeln gäller inte för strukturer som inte stöder direkt arv.

    Kompilatorer tillämpar vanligtvis den här regeln oberoende av CLS-efterlevnad, vilket visas i följande exempel. Den skapar en Doctor klass som härleds från en Person klass, men Doctor klassen kan inte anropa Person klasskonstruktorn för att initiera ärvda instansfält.

    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()
    '                  ~~~
    
  • Det går inte att anropa en objektkonstruktor förutom att skapa ett objekt. Dessutom kan ett objekt inte initieras två gånger. Det innebär till exempel att Object.MemberwiseClone och deserialiseringsmetoder inte får anropa konstruktorer.

Egenskaper

Egenskaper i CLS-kompatibla typer måste följa dessa regler:

  • En egenskap måste ha en setter, en getter eller båda. I en sammansättning implementeras dessa som särskilda metoder, vilket innebär att de visas som separata metoder (gettern heter get_propertyname och setter ärset_ propertyname) markerad som SpecialName i sammansättningens metadata. Kompilatorerna C# och Visual Basic tillämpar den här regeln automatiskt utan att attributet behöver tillämpas CLSCompliantAttribute .

  • En egenskaps typ är returtypen för egenskapsmottagaren och det sista argumentet i setter. Dessa typer måste vara CLS-kompatibla och argumenten kan inte tilldelas till egenskapen med referens (det vill: de kan inte vara hanterade pekare).

  • Om en egenskap har både en getter och en setter måste båda vara virtuella, både statiska eller båda instanserna. C#- och Visual Basic-kompilatorerna tillämpar automatiskt den här regeln via sin egenskapsdefinitionssyntax.

Händelser

En händelse definieras av dess namn och dess typ. Händelsetypen är ett ombud som används för att ange händelsen. Händelsen är till exempel AppDomain.AssemblyResolve av typen ResolveEventHandler. Förutom själva händelsen anger tre metoder med namn baserat på händelsenamnet händelsens implementering och markeras som SpecialName i sammansättningens metadata:

  • En metod för att lägga till en händelsehanterare med namnet add_EventName. Händelseprenumerationsmetoden för AppDomain.AssemblyResolve händelsen heter add_AssemblyResolvetill exempel .

  • En metod för att ta bort en händelsehanterare med namnet remove_EventName. Borttagningsmetoden för AppDomain.AssemblyResolve händelsen heter remove_AssemblyResolvetill exempel .

  • En metod för att ange att händelsen har inträffat med namnet raise_EventName.

Kommentar

De flesta av Common Language Specifications regler för händelser implementeras av språkkompilatorer och är transparenta för komponentutvecklare.

Metoderna för att lägga till, ta bort och höja händelsen måste ha samma tillgänglighet. Alla måste också vara statiska, instanser eller virtuella. Metoderna för att lägga till och ta bort en händelse har en parameter vars typ är händelsedelegattypen. Metoderna för att lägga till och ta bort måste båda vara närvarande eller båda vara frånvarande.

I följande exempel definieras en CLS-kompatibel klass med namnet Temperature som genererar en TemperatureChanged händelse om temperaturändringen mellan två avläsningar är lika med eller överskrider ett tröskelvärde. Klassen Temperature definierar uttryckligen en raise_TemperatureChanged metod så att den selektivt kan köra händelsehanterare.

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

Överlagringar

Common Language Specification ställer följande krav på överbelastade medlemmar:

  • Medlemmar kan överbelastas baserat på antalet parametrar och vilken typ av parameter som helst. Anropskonvention, returtyp, anpassade modifierare som tillämpas på metoden eller dess parameter och om parametrar skickas med värde eller referens beaktas inte vid differentiering mellan överlagringar. Ett exempel finns i koden för kravet att namn måste vara unika inom ett omfång i avsnittet Namngivningskonventioner .

  • Endast egenskaper och metoder kan överbelastas. Fält och händelser kan inte överbelastas.

  • Generiska metoder kan överbelastas baserat på antalet generiska parametrar.

Kommentar

Operatorerna op_Explicit och op_Implicit är undantag från regeln att returvärdet inte betraktas som en del av en metodsignatur för överbelastningsmatchning. Dessa två operatorer kan överbelastas baserat på både deras parametrar och deras returvärde.

Undantag

Undantagsobjekt måste härledas från System.Exception eller från en annan typ som härletts från System.Exception. I följande exempel visas kompilatorfelet som resulterar när en anpassad klass med namnet ErrorClass används för undantagshantering.

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

För att åtgärda det här felet ErrorClass måste klassen ärva från System.Exception. Dessutom måste egenskapen Message åsidosättas. I följande exempel korrigeras dessa fel för att definiera en ErrorClass klass som är CLS-kompatibel.

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

Attribut

I .NET-sammansättningar tillhandahåller anpassade attribut en utökningsbar mekanism för att lagra anpassade attribut och hämta metadata om programmeringsobjekt, till exempel sammansättningar, typer, medlemmar och metodparametrar. Anpassade attribut måste härledas från System.Attribute eller från en typ som härletts från System.Attribute.

Följande exempel bryter mot den här regeln. Den definierar en NumericAttribute klass som inte härleds från System.Attribute. Ett kompilatorfel resulterar bara när attributet som inte är CLS-kompatibelt tillämpas, inte när klassen har definierats.

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

Konstruktorn eller egenskaperna för ett CLS-kompatibelt attribut kan endast exponera följande typer:

I följande exempel definieras en DescriptionAttribute klass som härleds från attributet. Klasskonstruktorn har en parameter av typen Descriptor, så klassen är inte CLS-kompatibel. C#-kompilatorn genererar en varning men kompileras, medan Visual Basic-kompilatorn inte genererar någon varning eller ett fel.

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

ATTRIBUTEt CLSCompliantAttribute

Attributet CLSCompliantAttribute används för att ange om ett programelement uppfyller common language-specifikationen. Konstruktorn CLSCompliantAttribute(Boolean) innehåller en enda obligatorisk parameter, isCompliant, som anger om programelementet är CLS-kompatibelt.

Vid kompileringstillfället identifierar kompilatorn icke-kompatibla element som antas vara CLS-kompatibla och genererar en varning. Kompilatorn genererar inte varningar för typer eller medlemmar som uttryckligen deklareras vara inkompatibla.

Komponentutvecklare kan använda attributet på CLSCompliantAttribute två sätt:

  • Definiera de delar av det offentliga gränssnittet som exponeras av en komponent som är CLS-kompatibel och de delar som inte är CLS-kompatibla. När attributet används för att markera vissa programelement som CLS-kompatibla garanterar dess användning att dessa element är tillgängliga från alla språk och verktyg som är avsedda för .NET.

  • För att säkerställa att komponentbibliotekets offentliga gränssnitt endast exponerar programelement som är CLS-kompatibla. Om elementen inte är CLS-kompatibla utfärdar kompilatorerna vanligtvis en varning.

Varning

I vissa fall tillämpar språkkompilatorer CLS-kompatibla regler oavsett om CLSCompliantAttribute attributet används. Att definiera en statisk medlem i ett gränssnitt strider till exempel mot en CLS-regel. Om du i det här avseendet definierar en static (i C#) eller Shared (i Visual Basic)-medlem i ett gränssnitt, visar både C# och Visual Basic-kompilatorerna ett felmeddelande och kompilerar inte appen.

Attributet CLSCompliantAttribute är markerat med ett AttributeUsageAttribute attribut som har värdet AttributeTargets.All. Med det här värdet kan du använda CLSCompliantAttribute attributet för alla programelement, inklusive sammansättningar, moduler, typer (klasser, strukturer, uppräkningar, gränssnitt och ombud), typmedlemmar (konstruktorer, metoder, egenskaper, fält och händelser), parametrar, allmänna parametrar och returvärden. I praktiken bör du dock endast använda attributet för sammansättningar, typer och typmedlemmar. Annars ignorerar kompilatorer attributet och fortsätter att generera kompilatorvarningar när de stöter på en icke-kompatibel parameter, allmän parameter eller returvärde i bibliotekets offentliga gränssnitt.

Värdet för CLSCompliantAttribute attributet ärvs av inneslutna programelement. Om en sammansättning till exempel har markerats som CLS-kompatibel är dess typer även CLS-kompatibla. Om en typ har markerats som CLS-kompatibel är dess kapslade typer och medlemmar också CLS-kompatibla.

Du kan uttryckligen åsidosätta den ärvda efterlevnaden genom att tillämpa attributet på ett inneslutet CLSCompliantAttribute programelement. Du kan till exempel använda CLSCompliantAttribute attributet med värdet isCompliantfalse för för att definiera en icke-kompatibel typ i en kompatibel sammansättning, och du kan använda attributet med värdet isComplianttrue för för att definiera en kompatibel typ i en icke-kompatibel sammansättning. Du kan också definiera icke-kompatibla medlemmar i en kompatibel typ. En icke-kompatibel typ kan dock inte ha kompatibla medlemmar, så du kan inte använda attributet med värdet isComplianttrue för att åsidosätta arv från en icke-kompatibel typ.

När du utvecklar komponenter bör du alltid använda CLSCompliantAttribute attributet för att ange om din sammansättning, dess typer och dess medlemmar är CLS-kompatibla.

Så här skapar du CLS-kompatibla komponenter:

  1. CLSCompliantAttribute Använd för att markera sammansättningen som CLS-kompatibel.

  2. Markera alla offentligt exponerade typer i sammansättningen som inte är CLS-kompatibla som icke-kompatibla.

  3. Markera alla offentligt exponerade medlemmar i CLS-kompatibla typer som icke-kompatibla.

  4. Ange ett CLS-kompatibelt alternativ för icke-CLS-kompatibla medlemmar.

Om du har markerat alla dina icke-kompatibla typer och medlemmar bör kompilatorn inte utfärda några icke-efterlevnadsvarningar. Du bör dock ange vilka medlemmar som inte är CLS-kompatibla och lista sina CLS-kompatibla alternativ i produktdokumentationen.

I följande exempel används CLSCompliantAttribute attributet för att definiera en CLS-kompatibel sammansättning och en typ, CharacterUtilities, som har två icke-CLS-kompatibla medlemmar. Eftersom båda medlemmarna är taggade med CLSCompliant(false) attributet skapar kompilatorn inga varningar. Klassen innehåller också ett CLS-kompatibelt alternativ för båda metoderna. Vanligtvis lägger vi bara till två överlagringar i ToUTF16 metoden för att tillhandahålla CLS-kompatibla alternativ. Men eftersom metoderna inte kan överbelastas baserat på returvärdet skiljer sig namnen på cls-kompatibla metoder från namnen på de icke-kompatibla metoderna.

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

Om du utvecklar en app i stället för ett bibliotek (dvs. om du inte exponerar typer eller medlemmar som kan användas av andra apputvecklare) är CLS-efterlevnaden av programelementen som appen använder av endast av intresse om ditt språk inte stöder dem. I så fall genererar språkkompilatorn ett fel när du försöker använda ett icke-CLS-kompatibelt element.

Samverkan mellan språk

Språk oberoende har några möjliga betydelser. En betydelse omfattar sömlöst använda typer som skrivits på ett språk från en app som skrivits på ett annat språk. En andra innebörd, som är fokus för den här artikeln, är att kombinera kod som skrivits på flera språk till en enda .NET-sammansättning.

I följande exempel visas samverkan mellan språk genom att skapa ett klassbibliotek med namnet Utilities.dll som innehåller två klasser NumericLib och StringLib. Klassen NumericLib är skriven i C# och StringLib klassen är skriven i Visual Basic. Här är källkoden för StringUtil.vb, som innehåller en enskild medlem, ToTitleCase, i sin StringLib klass.

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

Här är källkoden för NumberUtil.cs, som definierar en NumericLib klass som har två medlemmar och IsEvenNearZero.

using System;

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

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

Om du vill paketera de två klasserna i en enda sammansättning måste du kompilera dem till moduler. Om du vill kompilera Visual Basic-källkodsfilen till en modul använder du det här kommandot:

vbc /t:module StringUtil.vb

Mer information om kommandoradssyntaxen för Visual Basic-kompilatorn finns i Skapa från kommandoraden.

Om du vill kompilera C#-källkodsfilen till en modul använder du det här kommandot:

csc /t:module NumberUtil.cs

Sedan använder du Linker-alternativen för att kompilera de två modulerna till en sammansättning:

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

I följande exempel anropas NumericLib.NearZero sedan metoderna och StringLib.ToTitleCase . Både Visual Basic-koden och C#-koden kan komma åt metoderna i båda klasserna.

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

Om du vill kompilera Visual Basic-koden använder du det här kommandot:

vbc example.vb /r:UtilityLib.dll

Kompilera med C#genom att ändra namnet på kompilatorn från vbc till cscoch ändra filnamnstillägget från .vb till .cs:

csc example.cs /r:UtilityLib.dll