Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
.NET ist sprachunabhängig. Dies bedeutet, dass Sie als Entwickler in einer der vielen Sprachen entwickeln können, die auf .NET-Implementierungen abzielen, z. B. C#, F# und Visual Basic. Sie können auf die Typen und Member von Klassenbibliotheken zugreifen, die für .NET-Implementierungen entwickelt wurden, ohne die Sprache kennen zu müssen, in der sie ursprünglich geschrieben wurden, und ohne die Konventionen der Originalsprache einhalten zu müssen. Wenn Sie ein Komponentenentwickler sind, kann unabhängig von ihrer Sprache von einer beliebigen .NET-App auf Ihre Komponente zugegriffen werden.
Hinweis
In diesem ersten Teil dieses Artikels werden die Erstellung sprachunabhängiger Komponenten erläutert, d. h. Komponenten, die von Apps genutzt werden können, die in einer beliebigen Sprache geschrieben sind. Sie können auch eine einzelne Komponente oder App aus Dem Quellcode erstellen, der in mehreren Sprachen geschrieben wurde; siehe Sprachübergreifende Interoperabilität im zweiten Teil dieses Artikels.
Um umfassend mit anderen Objekten zu interagieren, die in einer beliebigen Sprache geschrieben wurden, müssen Objekte nur solche Merkmale für Aufrufer verfügbar machen, die allen Sprachen gemeinsam sind. Dieser allgemeine Satz von Features wird durch die Common Language Specification (CLS) definiert, bei der es sich um eine Reihe von Regeln handelt, die für generierte Assemblys gelten. Die Common Language Specification wird in Partition I, Klauseln 7 bis 11 des ECMA-335 Standard: Common Language Infrastructure definiert.
Wenn Ihre Komponente der Common Language Specification entspricht, ist sie garantiert CLS-kompatibel und kann von Code in Assemblys aufgerufen werden, die in jeder Programmiersprache geschrieben sind, die die CLS unterstützt. Sie können bestimmen, ob ihre Komponente zur Kompilierungszeit der Common Language Specification entspricht, indem Sie das CLSCompliantAttribute-Attribut auf Den Quellcode anwenden. Weitere Informationen finden Sie unter "CLSCompliantAttribute"-Attribut.
CLS-Complianceregeln
In diesem Abschnitt werden die Regeln zum Erstellen einer CLS-kompatiblen Komponente erläutert. Eine vollständige Liste der Regeln finden Sie unter Partition I, Klausel 11 des ECMA-335 Standard: Common Language Infrastructure.
Hinweis
In der Common Language Specification wird jede Regel für die CLS-Compliance erläutert, da sie für Verbraucher gilt (Entwickler, die programmgesteuert auf eine Komponente zugreifen, die CLS-kompatibel ist), Frameworks (Entwickler, die einen Sprachcompiler verwenden, um CLS-kompatible Bibliotheken zu erstellen) und Extender (Entwickler, die ein Tool wie einen Sprachcompiler oder einen Codeparser erstellen, der CLS-kompatible Komponenten erstellt). Dieser Artikel konzentriert sich auf die Regeln, die für Frameworks gelten. Beachten Sie jedoch, dass einige der Regeln, die für Extender gelten, auch für Assemblys gelten, die mit Reflection.Emit erstellt werden.
Um eine Komponente zu entwerfen, die sprachunabhängig ist, müssen Sie nur die Regeln für die CLS-Compliance auf die öffentliche Schnittstelle Ihrer Komponente anwenden. Ihre private Implementierung muss nicht der Spezifikation entsprechen.
Von Bedeutung
Die Regeln für die CLS-Compliance gelten nur für die öffentliche Schnittstelle einer Komponente, nicht für die private Implementierung.
Beispielsweise sind andere nicht signierte ganze Zahlen als Byte nicht CLS-kompatibel. Da die Person
Klasse im folgenden Beispiel eine Age
Eigenschaft vom Typ UInt16verfügbar macht, zeigt der folgende Code eine Compilerwarnung an.
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
' ~~~
Sie können die Person
Klasse CLS-kompatibel machen, indem Sie den Typ der Age
Eigenschaft von UInt16 zu Int16, einer CLS-kompatiblen, 16-Bit-signierten Ganzzahl ändern. Sie müssen den Typ des privaten personAge
Felds nicht ändern.
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
Die öffentliche Schnittstelle einer Bibliothek besteht aus den folgenden Komponenten:
Definitionen öffentlicher Klassen.
Definitionen der öffentlichen Mitglieder öffentlicher Klassen und Definitionen von Mitgliedern, die für abgeleitete Klassen zugänglich sind (d. h. geschützte Mitglieder).
Parameter und Rückgabetypen öffentlicher Methoden öffentlicher Klassen sowie Parameter und Rückgabetypen von Methoden, auf die für abgeleitete Klassen zugegriffen werden kann.
Die Regeln für die CLS-Compliance sind in der folgenden Tabelle aufgeführt. Der Text der Regeln stammt aus dem ECMA-335 Standard: Common Language Infrastructure, das Copyright 2012 von Ecma International ist. Ausführlichere Informationen zu diesen Regeln finden Sie in den folgenden Abschnitten.
Kategorie | Weitere Informationen | Regel | Regelnummer |
---|---|---|---|
Zugänglichkeit | Zugänglichkeit für Mitglieder | Beim Überschreiben von geerbten Methoden darf der Zugriff nicht geändert werden, außer wenn eine Methode überschrieben wird, die von einer anderen Assembly mit family-or-assembly -Zugriff vererbt wurde. In diesem Fall muss für die Überschreibung family -Zugriff festgelegt werden. |
10 |
Zugänglichkeit | Zugänglichkeit für Mitglieder | Die Sichtbarkeit und Zugänglichkeit von Typen und Mitgliedern sollte so sein, dass Typen in der Signatur eines Mitglieds sichtbar und zugänglich sind, wenn das Mitglied selbst sichtbar und zugänglich ist. Eine öffentliche Methode, die außerhalb der Assembly sichtbar ist, darf z. B. kein Argument haben, dessen Typ nur innerhalb der Assembly sichtbar ist. Die Sichtbarkeit und Zugänglichkeit von Typen, die einen instanziierten generischen Typ bilden, der in der Signatur eines Mitglieds verwendet wird, müssen sichtbar und zugänglich sein, wann immer das Mitglied selbst sichtbar und zugänglich ist. Ein instanziierter generischer Typ, der in der Signatur eines Elements vorhanden ist, das außerhalb seiner Assembly sichtbar ist, darf nicht über ein generisches Argument verfügen, dessen Typ nur innerhalb der Assembly sichtbar ist. | 12 |
Felder | Felder | Arrays müssen Elemente mit einem CLS-kompatiblen Typ aufweisen, und alle Dimensionen des Arrays müssen untere Grenzen von Null aufweisen. Nur die Tatsache, dass ein Element ein Array ist und der Elementtyp des Arrays erforderlich ist, um zwischen Überladungen zu unterscheiden. Wenn die Überladung auf zwei oder mehr Arraytypen basiert, müssen die Elementtypen benannt werden. | 16 |
Attribute | Attribute | Attribute müssen vom Typ System.Attributeoder von einem Typ sein, der von ihr erbt. | 41 |
Attribute | Attribute | Die CLS lässt nur eine Teilmenge der Codierungen von benutzerdefinierten Attributen zu. Die einzigen Typen, die in diesen Codierungen angezeigt werden sollen (siehe Partition IV): System.Type, System.String, System.Char, System.Boolean, System.Byte, System.Int16, System.Int32, System.Int64, System.Single, System.Double und alle Enumerationen, die auf einem CLS-kompatiblen Basistyp für Ganzzahlen basieren. | 34 |
Attribute | Attribute | Die CLS lässt keine öffentlich sichtbaren erforderlichen Modifizierer (modreq , siehe Partition II) zu, erlaubt jedoch optionale Modifizierer (modopt , siehe Partition II), die es nicht versteht. |
35 |
Erbauer | Konstruktoren | Ein Objektkonstruktor ruft einen Instanzkonstruktor seiner Basisklasse auf, bevor ein Zugriff auf geerbte Instanzdaten erfolgt. (Dies gilt nicht für Werttypen, für die keine Konstruktoren vorhanden sind.) | 21 |
Erbauer | Konstruktoren | Ein Objektkonstruktor darf nicht aufgerufen werden, außer als Teil der Erstellung eines Objekts, und ein Objekt darf nicht zweimal initialisiert werden. | 22 |
Enumerationen | Enumerationen | Der zugrunde liegende Typ eines Enums muss ein integrierter CLS-Ganzzahltyp sein, der Name des Feldes muss "value__" sein, und dieses Feld muss mit RTSpecialName markiert sein. |
7 |
Enumerationen | Enumerationen | Es gibt zwei verschiedene Arten von Enumerationen, die durch das Vorhandensein oder Fehlen des System.FlagsAttribute benutzerdefinierten Attributs (siehe Partition IV Library) gekennzeichnet sind. Eins stellt benannte ganzzahlige Werte dar; die andere stellt benannte Bitkennzeichnungen dar, die kombiniert werden können, um einen unbenannten Wert zu generieren. Der Wert eines Werts enum ist nicht auf die angegebenen Werte beschränkt. |
8 |
Enumerationen | Enumerationen | Literale statische Felder eines Enums müssen den Typ des Enums selbst haben. | 9 |
Ereignisse | Ereignisse | Die Methoden, die ein Ereignis implementieren, müssen in den Metadaten markiert SpecialName werden. |
29 |
Ereignisse | Ereignisse | Die Zugänglichkeit eines Ereignisses und seiner Accessoren ist identisch. | 30 |
Ereignisse | Ereignisse | Die Methoden add und remove für ein Ereignis sollen beide entweder vorhanden sein oder fehlen. |
31 |
Ereignisse | Ereignisse | Die Methoden add und remove für ein Ereignis sollen jeweils einen Parameter nehmen, dessen Typ den Typ des Ereignisses definiert und der vom System.Delegate abgeleitet ist. |
32 |
Ereignisse | Ereignisse | Ereignisse müssen einem bestimmten Benennungsmuster entsprechen. Das in CLS-Regel 29 genannte SpecialName-Attribut wird in geeigneten Namensvergleichen ignoriert und muss den Bezeichnerregeln entsprechen. | 33 |
Ausnahmen | Ausnahmen | Objekte, die ausgelöst werden, sind vom Typ "System.Exception " oder ein Typ, der von ihr erbt. Dennoch sind CLS-kompatible Methoden nicht erforderlich, um die Verteilung anderer Arten von Ausnahmen zu blockieren. | 40 |
Allgemein | CLS-Complianceregeln | CLS-Regeln gelten nur für Teile eines Typs, auf die außerhalb der definierten Assembly zugegriffen oder sichtbar ist. | 1 |
Allgemein | CLS-Complianceregeln | Mitglieder nicht CLS-kompatibler Typen dürfen nicht als CLS-kompatibel gekennzeichnet sein. | 2 |
Generika | Generische Typen und Member | Geschachtelte Typen müssen mindestens so viele generische Parameter wie der eingeschlossene Typ aufweisen. Generische Parameter in einem geschachtelten Typ entsprechen positionsmäßig den generischen Parametern im umgebenden Typ. | 42 |
Generika | Generische Typen und Member | Der Name eines generischen Typs muss die Anzahl von Typparametern codieren, die für den nicht geschachtelten Typ deklariert sind, oder neu in den Typ eingeführt werden, wenn er geschachtelt ist, gemäß den oben definierten Regeln. | 43 |
Generika | Generische Typen und Member | Ein generischer Typ muss ausreichende Einschränkungen neu einschließen, um sicherzustellen, dass alle Einschränkungen für den Basistyp oder Schnittstellen durch die generischen Typeinschränkungen erfüllt werden. | 44 |
Generika | Generische Typen und Member | Typen, die als Einschränkungen für generische Parameter verwendet werden, müssen selbst CLS-konform sein. | 45 |
Generika | Generische Typen und Member | Die Sichtbarkeit und Zugänglichkeit von Elementen (einschließlich geschachtelter Typen) in einem instanziierten generischen Typ wird als auf die spezifische Instanziierung und nicht auf die generische Typdeklaration als Ganzes bezogen betrachtet. Vorausgesetzt, die Sichtbarkeits- und Barrierefreiheitsregeln der CLS-Regel 12 gelten weiterhin. | 46 |
Generika | Generische Typen und Member | Für jede abstrakte oder virtuelle generische Methode muss eine konkrete Standardimplementierung (nicht abstrakt) vorhanden sein. | 47 |
Schnittstellen | Schnittstellen | CLS-kompatible Schnittstellen erfordern nicht die Definition nicht CLS-konformer Methoden, um sie zu implementieren. | 18 |
Schnittstellen | Schnittstellen | CLS-kompatible Schnittstellen dürfen weder statische Methoden definieren noch Felder definieren. | 19 |
Elemente | Typmember im Allgemeinen | Globale statische Felder und Methoden sind nicht CLS-kompatibel. | 36 |
Elemente | -- | Der Wert einer Literalstatik wird mithilfe von Feldinitialisierungsmetadaten angegeben. Ein CLS-kompatibles Literal muss über einen Wert verfügen, der in den Feldinitialisierungsmetadaten angegeben wird, der genau vom gleichen Typ wie das Literal ist (oder des zugrunde liegenden Typs, wenn dieses Literal enum ist). |
13 |
Elemente | Typmember im Allgemeinen | Die Vararg-Einschränkung ist nicht Teil der CLS, und die einzige aufrufkonvention, die vom CLS unterstützt wird, ist die standardmäßige verwaltete Anrufkonvention. | 15 |
Benennungskonventionen | Namenskonventionen | Assemblys müssen Anhang 7 von Fachbericht 15 des Unicode Standard3.0 folgen, in dem der Satz von Zeichen geregelt wird, die am Anfang oder innerhalb von Bezeichnern enthalten sein dürfen. Er ist online unter Unicode Normalization Forms (Unicode-Normalisierungsformen) verfügbar. Bezeichner müssen im kanonischen Format vorliegen, das durch Unicode Normalisierungsform C definiert ist. Für CLS-Zwecke sind zwei Bezeichner identisch, wenn ihre Kleinbuchstabenzuordnungen, wie von den unicode-unabhängigen, 1-zu-1-Kleinbuchstabenzuordnungen angegeben, identisch sind. Demnach müssen sich zwei Bezeichner in mehr als nur der Großschreibung unterscheiden, damit sie gemäß der CLS als unterschiedlich angesehen werden können. Um jedoch eine geerbte Definition außer Kraft zu setzen, muss die CLI die genaue Codierung der ursprünglichen Deklaration verwenden. | 4 |
Überladen | Namenskonventionen | Alle Namen, die in einem CLS-kompatiblen Bereich eingeführt werden, müssen in ihrer Art eindeutig unabhängig sein, außer bei identischen Namen, die durch Überladen aufgelöst werden. Das heißt, während der CTS einen einzelnen Typ erlaubt, denselben Namen für eine Methode und ein Feld zu verwenden, die CLS nicht. | 5 |
Überladen | Namenskonventionen | Felder und geschachtelte Typen müssen allein durch den Bezeichnervergleich unterschieden werden, obwohl das CTS unterschiedliche Signaturen unterscheiden kann. Methoden, Eigenschaften und Ereignisse mit demselben Namen (nach Bezeichnervergleich) sollten sich in mehr als nur dem Rückgabetyp unterscheiden, es sei denn, wie in CLS-Regel 39 angegeben. | 6 |
Überladen | Überladungen | Nur Eigenschaften und Methoden können überladen werden. | 37 |
Überladen | Überladungen | Eigenschaften und Methoden können nur basierend auf der Anzahl und den Typen ihrer Parameter überladen werden, mit Ausnahme der benannten Konvertierungsoperatoren op_Implicit und op_Explicit , die auch basierend auf ihrem Rückgabetyp überladen werden können. |
38 |
Überladen | -- | Wenn zwei oder mehr CLS-kompatible Methoden, die in einem Typ deklariert sind, denselben Namen haben und für einen bestimmten Satz von Typinstanziierungen dieselben Parameter und Rückgabetypen aufweisen, müssen alle diese Methoden semantisch mit diesen Typinstanziationen übereinstimmen. | 48 |
Eigenschaften | Eigenschaften | Die Methoden, die die Getter- und Settermethoden einer Eigenschaft implementieren, müssen in den Metadaten markiert SpecialName werden. |
24 |
Eigenschaften | Eigenschaften | Die Zugriffsmethoden einer Eigenschaft müssen alle „static“, alle „virtual“ oder alle „instance“ sein. | 26 |
Eigenschaften | Eigenschaften | Der Typ einer Eigenschaft muss dem Rückgabetyp der Getter-Methode und dem Typ des letzten Arguments der Setter-Methode entsprechen. Die Parametertypen der Eigenschaft müssen den Parametertypen der Getter-Methode und, ausgenommen dem Letzten, allen Parametertypen der Setter-Methode entsprechen. Diese Typen müssen CLS-kompatibel sein und dürfen keine verwalteten Zeiger sein (d.h., sie dürfen nicht als Verweise übergeben werden). | 27 |
Eigenschaften | Eigenschaften | Eigenschaften müssen einem bestimmten Benennungsmuster entsprechen. Das SpecialName attribut gemäß CLS-Regel 24 wird in geeigneten Namensvergleichen ignoriert und muss den Bezeichnerregeln entsprechen. Eine Eigenschaft verfügt über eine Getter-Methode, eine Setter-Methode oder beide. |
28 |
Typenumrechnung | Typkonvertierung | Wenn entweder op_Implicit oder op_Explicit bereitgestellt wird, muss eine alternative Möglichkeit für die Umwandlung bereitgestellt werden. | 39 |
Typen | Typen und Typmembersignaturen | Geschachtelte Werttypen sind nicht CLS-kompatibel. | 3 |
Typen | Typen und Typmembersignaturen | Alle Typen, die in einer Signatur angezeigt werden, müssen CLS-konform sein. Alle Typen, die einen instanziierten generischen Typ erstellen, müssen CLS-kompatibel sein. | 11 |
Typen | Typen und Typmembersignaturen | Typisierte Verweise sind nicht CLS-kompatibel. | 14 |
Typen | Typen und Typmembersignaturen | Nicht verwaltete Zeigertypen sind nicht CLS-kompatibel. | 17 |
Typen | Typen und Typmembersignaturen | CLS-kompatible Klassen, Werttypen und Schnittstellen erfordern nicht die Implementierung nicht CLS-kompatibler Member. | 20 |
Typen | Typen und Typmembersignaturen | System.Object ist CLS-kompatibel. Jede andere CLS-kompatible Klasse erbt von einer CLS-kompatiblen Klasse. | 23 |
Index zu Unterabschnitten:
- Typen und Typmembersignaturen
- Namenskonventionen
- Typkonvertierung
- Felder
- Schnittstellen
- Enumerationen
- Typmember im Allgemeinen
- Zugänglichkeit für Mitglieder
- Generische Typen und Member
- Konstruktoren
- Eigenschaften
- Ereignisse
- Überladungen
- Ausnahmen
- Attribute
Typen und Typmembersignaturen
Der System.Object-Typ ist CLS-kompatibel und ist der Basistyp aller Objekttypen im .NET-Typsystem. Die Vererbung in .NET ist entweder implizit (z. B. die String-Klasse erbt implizit von der Object
Klasse) oder explizit (z. B. die CultureNotFoundException-Klasse erbt explizit von der ArgumentException-Klasse , die explizit von der Exception-Klasse erbt. Damit ein abgeleiteter Typ CLS kompatibel ist, muss der Basistyp auch CLS-kompatibel sein.
Das folgende Beispiel zeigt einen abgeleiteten Typ, dessen Basistyp nicht CLS-kompatibel ist. Sie definiert eine Basisklasse Counter
, die eine nicht signierte 32-Bit-Ganzzahl als Zähler verwendet. Da die Klasse Zählerfunktionalität bereitstellt, indem sie eine nicht signierte Ganzzahl kapselt, wird die Klasse als nicht CLS-konform markiert. Daher ist eine abgeleitete Klasse NonZeroCounter
nicht 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
' ~~~~~~~~~~~~~~
Alle Typen, die in Membersignaturen angezeigt werden, einschließlich des Rückgabetyps einer Methode oder eines Eigenschaftstyps, müssen CLS-kompatibel sein. Darüber hinaus gilt Folgendes für generische Typen:
Alle Typen, die einen instanziierten generischen Typ bilden, müssen CLS-konform sein.
Alle Typen, die als Einschränkungen für generische Parameter verwendet werden, müssen CLS-kompatibel sein.
Das allgemeine . NET-Typsystem enthält viele integrierte Typen, die direkt von der Common Language Runtime unterstützt werden und speziell in den Metadaten einer Assembly codiert werden. Von diesen systeminternen Typen sind die in der folgenden Tabelle aufgeführten Typen CLS-kompatibel.
CLS-kompatibler Typ | BESCHREIBUNG |
---|---|
Byte- | 8-Bit-ganzzahl ohne Vorzeichen |
Int16 | 16-Bit-Ganzzahl mit Vorzeichen |
INT32 | 32-Bit-Ganzzahl mit Vorzeichen |
Int64 | 64-Bit-Ganzzahl mit Vorzeichen |
Hälfte | Gleitkommawert mit halber Genauigkeit |
Ledig | Gleitkommawert mit einfacher Genauigkeit |
Doppel | Gleitkommawert mit doppelter Genauigkeit |
Boolesch | TRUE- oder FALSE-Werttyp |
Verkohlen | UTF-16-codierte Codeeinheit |
Dezimal | Dezimalzahl ohne Gleitkomma |
IntPtr | Zeiger oder Handle einer Plattform-definierten Größe |
Schnur | Sammlung von null, einem oder mehreren Char-Objekten |
Die in der folgenden Tabelle aufgeführten systeminternen Typen sind nicht CLS-kompatibel.
Nicht kompatibler Typ | BESCHREIBUNG | CLS-kompatible Alternative |
---|---|---|
SByte | Ganzzahliger 8-Bit-Datentyp mit Vorzeichen | Int16 |
UInt16- | 16-Bit-ganzzahl ohne Vorzeichen | INT32 |
UInt32- | 32-Bit-Ganzzahl ohne Vorzeichen | Int64 |
UInt64- | 64-Bit-Ganzzahl ohne Vorzeichen | Int64 (kann überlaufen), BigInteger oder Double |
UIntPtr | Nicht signierter Zeiger oder Handle | IntPtr |
Die .NET-Klassenbibliothek oder eine andere Klassenbibliothek kann andere Typen enthalten, die nicht CLS-kompatibel sind, z. B.:
Geschachtelte Werttypen Im folgenden C#-Beispiel wird eine Klasse erstellt, die eine öffentliche Eigenschaft vom Typ
int*
"namedValue
" aufweist. Da es sichint*
um einen Boxwerttyp handelt, kennzeichnet der Compiler ihn als nicht 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
Typierte Verweise, die spezielle Konstrukte sind, die einen Verweis auf ein Objekt und einen Verweis auf einen Typ enthalten. Typierte Verweise werden in .NET durch die TypedReference Klasse dargestellt.
Wenn ein Typ nicht CLS-kompatibel ist, sollten Sie das CLSCompliantAttribute-Attribut mit einem isCompliant
-Wert von false
anwenden. Weitere Informationen finden Sie im Abschnitt "CLSCompliantAttribute-Attribut".
Im folgenden Beispiel wird das Problem der CLS-Compliance in einer Methodensignatur und in der generischen Typinstanziierung veranschaulicht. Sie definiert eine InvoiceItem
Klasse mit einer Eigenschaft vom Typ UInt32, einer Eigenschaft vom Typ Nullable<UInt32>
und einem Konstruktor mit Parametern vom Typ UInt32 und Nullable<UInt32>
. Sie erhalten vier Compilerwarnungen, wenn Sie versuchen, dieses Beispiel zu kompilieren.
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
' ~~~~~~~~~
Um die Compilerwarnungen zu beseitigen, ersetzen Sie die nicht CLS-kompatiblen Typen in der InvoiceItem
öffentlichen Schnittstelle durch kompatible Typen:
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
Zusätzlich zu den aufgeführten spezifischen Typen sind einige Typen nicht CLS-kompatibel. Dazu gehören nicht verwaltete Zeigertypen und Funktionszeigertypen. Im folgenden Beispiel wird eine Compilerwarnung generiert, da sie einen Zeiger auf eine ganze Zahl verwendet, um ein Array mit ganzen Zahlen zu erstellen.
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
Bei CLS-kompatiblen abstrakten Klassen (d. h. Klassen, die als abstract
C# oder als MustInherit
Visual Basic gekennzeichnet sind), müssen alle Member der Klasse auch CLS-kompatibel sein.
Benennungskonventionen
Da bei einigen Programmiersprachen die Groß- und Kleinschreibung nicht beachtet werden muss, müssen Bezeichner (wie die Namen von Namespaces, Typen und Membern) durch mehr als die Groß-/Kleinschreibung unterschieden werden. Zwei Bezeichner werden als gleichwertig betrachtet, wenn ihre Kleinbuchstabenzuordnungen identisch sind. Das folgende C#-Beispiel definiert zwei öffentliche Klassen Person
und person
. Da sie sich nur durch die Groß-/Kleinschreibung unterscheiden, markiert der C#-Compiler sie als nicht CLS-kompatibel.
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)
Programmiersprachenbezeichner, z. B. die Namen von Namespaces, Typen und Membern, müssen dem Unicode-Standard entsprechen. Dies bedeutet Folgendes:
Das erste Zeichen eines Bezeichners kann ein beliebiger Unicode-Großbuchstabe, Kleinbuchstabe, Versalbuchstabe, Modifikationsbuchstabe, anderer Buchstabe oder Buchstabenzahl sein. Informationen zu Unicode-Zeichenkategorien finden Sie in der System.Globalization.UnicodeCategory Enumeration.
Nachfolgende Zeichen können aus einer der Kategorien wie das erste Zeichen stammen, und sie können auch Markierungen ohne Zwischenraum, Sperrmarkierungen, Dezimalwerte, Connectorinterpunktionen und Formatierungscodes umfassen.
Bevor Sie Bezeichner vergleichen, sollten Sie Formatierungscodes herausfiltern und die Bezeichner in Unicode Normalization Form C konvertieren, da ein einzelnes Zeichen durch mehrere UTF-16-codierte Codeeinheiten dargestellt werden kann. Zeichensequenzen, die dieselben Codeeinheiten in Unicode Normalization Form C erzeugen, sind nicht CLS-kompatibel. Im folgenden Beispiel wird eine Eigenschaft namens Å
definiert, die aus dem Zeichen ANGSTROM SIGN (U+212B) und einer zweiten Eigenschaft namens Å
besteht, die aus dem Zeichen LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5) besteht. Sowohl die C#- als auch die Visual Basic-Compiler kennzeichnen den Quellcode als nicht 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
' ~
Membernamen innerhalb eines bestimmten Bereichs (wie den Namespaces innerhalb einer Assembly, den Typen in einem Namespace oder den Membern innerhalb eines Typs) müssen eindeutig sein, abgesehen von Namen, die durch Überladen aufgelöst werden. Diese Anforderung ist strenger als das des allgemeinen Typsystems, das es mehreren Mitgliedern innerhalb eines Bereichs ermöglicht, identische Namen zu haben, solange sie unterschiedliche Arten von Membern sind (z. B. eine Methode und eine ist ein Feld). Insbesondere für Typmember:
Felder und geschachtelte Typen werden allein durch Namen unterschieden.
Methoden, Eigenschaften und Ereignisse mit demselben Namen müssen sich durch mehr als nur den Rückgabetyp unterscheiden.
Das folgende Beispiel veranschaulicht die Anforderung, dass Mitgliedsnamen innerhalb ihres jeweiligen Gültigkeitsbereichs eindeutig sein müssen. Es definiert eine Klasse namens Converter
, die vier Member mit dem Namen Conversion
enthält. Drei sind Methoden, und eine ist eine Eigenschaft. Die Methode, die einen Int64 Parameter enthält, ist mit einem eindeutigen Namen versehen, aber die beiden Methoden mit einem Int32 Parameter sind es nicht, da der Rückgabewert nicht als Teil der Signatur eines Mitglieds betrachtet wird. Die Conversion
Eigenschaft verstößt auch gegen diese Anforderung, da Eigenschaften nicht denselben Namen wie überladene Methoden haben können.
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
' ~~~~~~~~~~
Einzelne Sprachen enthalten eindeutige Schlüsselwörter, sodass Sprachen, die auf die Common Language Runtime abzielen, auch einen Mechanismus zum Verweisen auf Bezeichner (z. B. Typnamen) bereitstellen müssen, die mit Schlüsselwörtern übereinstimmen. Beispielsweise case
ist ein Schlüsselwort in C# und Visual Basic. Das folgende Beispiel aus Visual Basic kann jedoch mithilfe von geöffneten und geschlossenen geschweiften Klammern eine Klasse namens case
von dem Schlüsselwort case
unterscheiden. Andernfalls würde das Beispiel die Fehlermeldung "Schlüsselwort ist nicht gültig als Bezeichner" erzeugt und nicht kompiliert.
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
Im folgenden C#-Beispiel kann die Klasse mithilfe des case
@
Symbols instanziiert werden, um den Bezeichner aus dem Sprachschlüsselwort zu unterscheiden. Ohne sie würde der C#-Compiler zwei Fehlermeldungen anzeigen: "Typ erwartet" und "Ungültiger Ausdrucksbegriff 'case'."
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
Typenumrechnung
Die Spezifikation für gemeinsame Sprachen definiert zwei Konvertierungsoperatoren:
op_Implicit
, die für Erweiterungskonvertierungen verwendet wird, die nicht zu Datenverlust oder Genauigkeitseinbußen führen. Beispielsweise enthält die Decimal Struktur einen überladenenop_Implicit
Operator, um Werte von integralen Typen und Char Werten in Werte zu Decimal konvertieren.op_Explicit
, die für schmale Konvertierungen verwendet wird, die zu einem Verlust der Größe führen können (ein Wert wird in einen Wert konvertiert, der einen kleineren Bereich hat) oder genauigkeit. Die Struktur enthält beispielsweise einen überladenen Decimal-Operator, umop_Explicit
- und Double-Werte in Single zu konvertieren und um Decimal-Werte in integrale Werte wie Decimal, Double und Single zu konvertieren.
Allerdings unterstützen nicht alle Sprachen die Operatorüberladung oder die Definition von benutzerdefinierten Operatoren. Wenn Sie sich für die Implementierung dieser Konvertierungsoperatoren entscheiden, sollten Sie auch eine alternative Möglichkeit zum Ausführen der Konvertierung bereitstellen. Wir empfehlen, dass Sie From
Xxx- und To
Xxx-Methoden bereitstellen.
Im folgenden Beispiel werden CLS-konforme implizite und explizite Konvertierungen definiert. Es erstellt eine UDouble
Klasse, die eine nicht signierte Gleitkommazahl mit doppelter Genauigkeit darstellt. Sie bietet implizite Konvertierungen von UDouble
zu Double und für explizite Konvertierungen von UDouble
zu Single, Double zu UDouble
, und Single zu UDouble
. Außerdem definiert sie eine ToDouble
Methode als Alternative zum impliziten Konvertierungsoperator und den ToSingle
Methoden FromDouble
und FromSingle
Methoden als Alternativen zu den expliziten Konvertierungsoperatoren.
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
Felder
CLS-kompatible Arrays entsprechen den folgenden Regeln:
Alle Dimensionen eines Arrays müssen eine untere Grenze von Null aufweisen. Im folgenden Beispiel wird ein nicht CLS-kompatibles Array mit einer unteren Grenze von 1 erstellt. Trotz des Vorhandenseins des CLSCompliantAttribute Attributs erkennt der Compiler nicht, dass das von der
Numbers.GetTenPrimes
Methode zurückgegebene Array nicht CLS-kompatibel ist.[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
Alle Arrayelemente müssen aus CLS-kompatiblen Typen bestehen. Im folgenden Beispiel werden zwei Methoden definiert, die nicht CLS-kompatible Arrays zurückgeben. Der erste Gibt ein Array von UInt32 Werten zurück. Die zweite gibt ein Object Array zurück, das Int32- und UInt32-Werte enthält. Obwohl der Compiler das erste Array aufgrund seines UInt32 Typs nicht kompatibel identifiziert, erkennt er nicht, dass das zweite Array nicht CLS-kompatible Elemente enthält.
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() ' ~~~~~~~~~~~~
Die Überladungsauflösung für Methoden mit Arrayparametern basiert auf der Tatsache, dass es sich um Arrays und ihren Elementtyp handelt. Aus diesem Grund ist die folgende Definition einer überladenen
GetSquares
Methode 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
Schnittstellen
CLS-kompatible Schnittstellen können Eigenschaften, Ereignisse und virtuelle Methoden (Methoden ohne Implementierung) definieren. Eine CLS-kompatible Schnittstelle darf keine der folgenden Elemente aufweisen:
Statische Methoden oder statische Felder. Sowohl die C#- als auch die Visual Basic-Compiler generieren Compilerfehler, wenn Sie ein statisches Element in einer Schnittstelle definieren.
Felder Sowohl die C#- als auch die Visual Basic-Compiler generieren Compilerfehler, wenn Sie ein Feld in einer Schnittstelle definieren.
Methoden, die nicht CLS-kompatibel sind. Die folgende Schnittstellendefinition enthält z. B. eine Methode,
INumber.GetUnsigned
die als nicht CLS-kompatibel gekennzeichnet ist. In diesem Beispiel wird eine Compilerwarnung generiert.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 ' ~~~~~~~~~~~
Aufgrund dieser Regel, ist es nicht erforderlich, dass CLS-kompatible Typen nicht CLS-kompatible Member implementieren. Wenn ein CLS-kompatibles Framework eine Klasse verfügbar macht, die eine nicht CLS-kompatible Schnittstelle implementiert, sollte es auch konkrete Implementierungen aller nicht CLS-kompatiblen Member bereitstellen.
CLS-kompatible Sprachcompiler müssen auch zulassen, dass eine Klasse separate Implementierungen von Membern mit demselben Namen und derselben Signatur in mehreren Schnittstellen bereitstellt. Sowohl C# als auch Visual Basic unterstützen explizite Schnittstellenimplementierungen , um verschiedene Implementierungen identisch benannter Methoden bereitzustellen. Visual Basic unterstützt auch das Implements
Schlüsselwort, mit dem Sie explizit festlegen können, welche Schnittstelle und welches Mitglied ein bestimmtes Element implementiert. Im folgenden Beispiel wird dieses Szenario veranschaulicht, indem eine Temperature
Klasse definiert wird, die die ICelsius
Schnittstellen IFahrenheit
als explizite Schnittstellenimplementierungen implementiert.
using System;
[assembly: CLSCompliant(true)]
public interface IFahrenheit
{
decimal GetTemperature();
}
public interface ICelsius
{
decimal GetTemperature();
}
public class Temperature : ICelsius, IFahrenheit
{
private decimal _value;
public Temperature(decimal value)
{
// We assume that this is the Celsius value.
_value = value;
}
decimal IFahrenheit.GetTemperature()
{
return _value * 9 / 5 + 32;
}
decimal ICelsius.GetTemperature()
{
return _value;
}
}
public class Example
{
public static void Main()
{
Temperature temp = new Temperature(100.0m);
ICelsius cTemp = temp;
IFahrenheit fTemp = temp;
Console.WriteLine($"Temperature in Celsius: {cTemp.GetTemperature()} degrees");
Console.WriteLine($"Temperature in Fahrenheit: {fTemp.GetTemperature()} degrees");
}
}
// The example displays the following output:
// Temperature in Celsius: 100.0 degrees
// Temperature in Fahrenheit: 212.0 degrees
<Assembly: CLSCompliant(True)>
Public Interface IFahrenheit
Function GetTemperature() As Decimal
End Interface
Public Interface ICelsius
Function GetTemperature() As Decimal
End Interface
Public Class Temperature : Implements ICelsius, IFahrenheit
Private _value As Decimal
Public Sub New(value As Decimal)
' We assume that this is the Celsius value.
_value = value
End Sub
Public Function GetFahrenheit() As Decimal _
Implements IFahrenheit.GetTemperature
Return _value * 9 / 5 + 32
End Function
Public Function GetCelsius() As Decimal _
Implements ICelsius.GetTemperature
Return _value
End Function
End Class
Module Example
Public Sub Main()
Dim temp As New Temperature(100.0d)
Console.WriteLine("Temperature in Celsius: {0} degrees",
temp.GetCelsius())
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
temp.GetFahrenheit())
End Sub
End Module
' The example displays the following output:
' Temperature in Celsius: 100.0 degrees
' Temperature in Fahrenheit: 212.0 degrees
Enumerationen
CLS-kompatible Enumerationen müssen den folgenden Regeln entsprechen:
Der zugrunde liegende Typ der Enumeration muss eine systeminterne CLS-kompatible ganze Zahl (Byte, Int16, , Int32oder Int64) sein. Der folgende Code versucht beispielsweise, eine Aufzählung zu definieren, deren zugrunde liegender Typ ist UInt32 und eine Compilerwarnung generiert.
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 ' ~~~~
Ein Enumerationstyp muss ein einzelnes Instanzfeld
Value__
aufweisen, das mit dem FieldAttributes.RTSpecialName Attribut gekennzeichnet ist. Auf diese Weise können Sie implizit auf den Feldwert verweisen.Eine Aufzählung enthält literale statische Felder, deren Typen dem Typ der Enumeration selbst entsprechen. Wenn Sie z. B. eine
State
-Aufzählung mit den WertenState.On
undState.Off
definieren, sindState.On
undState.Off
beide literale statische Felder, deren TypState
ist.Es gibt zwei Arten von Enumerationen:
Eine Aufzählung, die einen Satz sich gegenseitig ausschließenden, benannten ganzzahligen Werten darstellt. Dieser Enumerationstyp wird durch das Fehlen des System.FlagsAttribute benutzerdefinierten Attributs angegeben.
Eine Aufzählung, die eine Gruppe von Bitkennzeichnungen darstellt, die kombiniert werden können, um einen unbenannten Wert zu generieren. Dieser Enumerationstyp wird durch das Vorhandensein des System.FlagsAttribute benutzerdefinierten Attributs angegeben.
Weitere Informationen finden Sie in der Dokumentation zur Enum Struktur.
Der Wert einer Aufzählung ist nicht auf den Bereich der angegebenen Werte beschränkt. Mit anderen Worten, der Wertebereich in einer Enumeration ist der Bereich seines zugrunde liegenden Werts. Mit der Enum.IsDefined Methode können Sie ermitteln, ob ein angegebener Wert ein Element einer Enumeration ist.
Typmember im Allgemeinen
Für die Common Language Specification muss auf alle Felder und Methoden als Mitglieder einer bestimmten Klasse zugegriffen werden. Daher sind globale statische Felder und Methoden (d. h. statische Felder oder Methoden, die außer einem Typ definiert sind) nicht CLS-kompatibel. Wenn Sie versuchen, ein globales Feld oder eine globale Methode in den Quellcode einzuschließen, generieren sowohl die C#- als auch die Visual Basic-Compiler einen Compilerfehler.
Die Spezifikation für allgemeine Sprachen unterstützt nur die standardverwaltete Anrufkonvention. Es unterstützt keine nicht verwalteten Aufrufkonventionen und -methoden mit variablen Argumentlisten, die mit dem varargs
Schlüsselwort gekennzeichnet sind. Verwenden Sie für Variablenargumentlisten, die mit der Standardkonvention für verwaltete Aufrufe kompatibel sind, das ParamArrayAttribute Attribut oder die Implementierung der einzelnen Sprache, z. B. das params
Schlüsselwort in C# und das ParamArray
Schlüsselwort in Visual Basic.
Memberzugriff
Das Überschreiben eines geerbten Elements kann die Zugänglichkeit dieses Elements nicht ändern. Beispielsweise kann eine öffentliche Methode in einer Basisklasse nicht von einer privaten Methode in einer abgeleiteten Klasse überschrieben werden. Es gibt eine Ausnahme: ein protected internal
(in C#) oder Protected Friend
(in Visual Basic)-Member in einer Assembly, die von einem Typ in einer anderen Assembly außer Kraft gesetzt wird. In diesem Fall ist der Zugriff auf die Überschreibung Protected
.
Im folgenden Beispiel wird der Fehler veranschaulicht, der auftritt, wenn das Attribut CLSCompliantAttribute auf true
festgelegt ist und Human
, eine Klasse, die von Animal
abgeleitet ist, versucht, die Zugriffsebene der Eigenschaft Species
von öffentlich auf privat zu ändern. Das Beispiel wird erfolgreich kompiliert, wenn seine Sichtbarkeit auf öffentlich geändert wird.
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
Typen in der Signatur eines Mitglieds müssen jederzeit zugänglich sein, wenn dieses Mitglied zugänglich ist. Dies bedeutet beispielsweise, dass ein öffentliches Mitglied keinen Parameter enthalten kann, dessen Typ privat, geschützt oder intern ist. Das folgende Beispiel veranschaulicht den Compilerfehler, der resultiert, wenn ein StringWrapper
Klassenkonstruktor einen internen StringOperationType
Enumerationswert verfügbar macht, der bestimmt, wie ein Zeichenfolgenwert umbrochen werden soll.
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)
' ~~~~~~~~~~~~~~~~~~~
Generische Typen und Mitglieder
Geschachtelte Typen verfügen immer über mindestens so viele generische Parameter wie der eingeschlossene Typ. Diese entsprechen in ihrer Position den generischen Parametern im umschließenden Typ. Der generische Typ kann auch neue generische Parameter enthalten.
Die Beziehung zwischen den generischen Typparametern eines enthaltenden Typs und seiner geschachtelten Typen kann durch die Syntax einzelner Sprachen ausgeblendet werden. Im folgenden Beispiel enthält ein generischer Typ Outer<T>
zwei geschachtelte Klassen Inner1A
und Inner1B<U>
. Die Aufrufe der Methode ToString
, die jede Klasse von Object.ToString() erbt, zeigen, dass jede geschachtelte Klasse die Typparameter ihrer enthaltenden Klasse enthält.
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]
Generische Typnamen werden im Formularnamen 'n' codiert, wobei name der Typname ist, ' ein Zeichenliteral ist, und n ist die Anzahl der Parameter, die für den Typ deklariert wurden, oder für geschachtelte generische Typen die Anzahl der neu eingeführten Typparameter. Diese Codierung generischer Typnamen ist in erster Linie für Entwickler interessant, die Reflektion verwenden, um auf generische CLS-Beschwerdetypen in einer Bibliothek zuzugreifen.
Wenn Einschränkungen auf einen generischen Typ angewendet werden, müssen alle als Einschränkungen verwendeten Typen auch CLS-kompatibel sein. Im folgenden Beispiel wird eine Klasse namens BaseClass
definiert, die nicht CLS-kompatibel ist, sowie eine generische Klasse namens BaseCollection
, deren Typparameter von BaseClass
abgeleitet sein muss. Da BaseClass
nicht CLS-kompatibel ist, gibt der Compiler eine Warnung aus.
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)
' ~~~~~~~~~
Wenn ein generischer Typ von einem generischen Basistyp abgeleitet wird, muss er alle Einschränkungen neu deklarieren, damit er garantieren kann, dass Einschränkungen für den Basistyp ebenfalls erfüllt sind. Im folgenden Beispiel wird ein Number<T>
definiert, der einen beliebigen numerischen Typ darstellen kann. Außerdem wird eine FloatingPoint<T>
Klasse definiert, die einen Gleitkommawert darstellt. Der Quellcode kann jedoch nicht kompiliert werden, da die Einschränkung, dass T ein Werttyp sein muss, nicht auf Number<T>
und FloatingPoint<T>
angewendet wird.
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)
' ~
Das Beispiel kompiliert erfolgreich, wenn die Einschränkung der FloatingPoint<T>
Klasse hinzugefügt wird.
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
Die Common Language Specification erzwingt ein konservatives Pro-Instanziierungsmodell für geschachtelte Typen und geschützte Member. Offene generische Typen dürfen keine Felder oder Mitglieder mit Signaturen offenlegen, die eine spezifische Instanziierung eines eingebetteten, geschützten generischen Typs enthalten. Nicht generische Typen, die eine bestimmte Instanziierung einer generischen Basisklasse oder Schnittstelle erweitern, können keine Felder oder Member mit Signaturen bereitstellen, die eine andere Instanziierung eines geschachtelten, geschützten generischen Typs enthalten.
Im folgenden Beispiel wird ein generischer Typ C1<T>
(oder C1(Of T)
in Visual Basic) und eine geschützte Klasse C1<T>.N
(oder C1(Of T).N
in Visual Basic) definiert.
C1<T>
verfügt über zwei Methoden M1
und M2
.
M1
ist jedoch nicht CLS-kompatibel, da versucht wird, ein C1<int>.N
(oder C1(Of Integer).N
) Objekt von C1<T> (oder C1(Of T)
) zurückzugeben. Eine zweite Klasse , C2
wird von C1<long>
(oder C1(Of Long)
) abgeleitet. Es hat zwei Methoden, M3
und M4
.
M3
ist nicht CLS-kompatibel, da versucht wird, ein C1<int>.N
(oder C1(Of Integer).N
) Objekt aus einer Unterklasse von C1<long>
zurückzugeben. Sprachcompiler können noch restriktiver sein. In diesem Beispiel zeigt Visual Basic einen Fehler an, wenn versucht wird, zu kompilieren 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)
' ~~~~~~~~~~~~~
Erbauer
Konstruktoren in CLS-kompatiblen Klassen und Strukturen müssen den folgenden Regeln entsprechen:
Ein Konstruktor einer abgeleiteten Klasse muss den Instanzkonstruktor seiner Basisklasse aufrufen, bevor er auf geerbte Instanzdaten zugreift. Diese Anforderung liegt daran, dass Basisklassenkonstruktoren nicht von ihren abgeleiteten Klassen geerbt werden. Diese Regel gilt nicht für Strukturen, die keine direkte Vererbung unterstützen.
In der Regel erzwingen Compiler diese Regel unabhängig von der CLS-Compliance, wie im folgenden Beispiel gezeigt. Sie erstellt eine
Doctor
Von einerPerson
Klasse abgeleitete Klasse, aber dieDoctor
Klasse ruft denPerson
Klassenkonstruktor nicht auf, um geerbte Instanzfelder zu initialisieren.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() ' ~~~
Ein Objektkonstruktor kann nicht aufgerufen werden, außer ein Objekt zu erstellen. Darüber hinaus kann ein Objekt nicht zweimal initialisiert werden. Dies bedeutet beispielsweise, dass Object.MemberwiseClone und Deserialisierungsmethoden keine Konstruktoren aufrufen dürfen.
Eigenschaften
Eigenschaften in CLS-kompatiblen Typen müssen den folgenden Regeln entsprechen:
Eine Eigenschaft muss über einen Setter, einen Getter oder beides verfügen. In einer Assembly werden diese als spezielle Methoden implementiert, was bedeutet, dass sie als separate Methoden angezeigt werden (der Getter ist
get_
propertyname und der Setter istset_
propertyname), die in den Metadaten der Assembly alsSpecialName
gekennzeichnet sind. Die C#- und Visual Basic-Compiler erzwingen diese Regel automatisch, ohne dass das CLSCompliantAttribute Attribut angewendet werden muss.Ein Typ der Eigenschaft entspricht dem Rückgabetyp der Getter-Methode der Eigenschaft und dem letzten Argument der Setter-Methode. Diese Typen müssen CLS-kompatibel sein, und Argumente können der Eigenschaft nicht anhand eines Verweises zugewiesen werden (d. a. sie können keine verwalteten Zeiger sein).
Wenn eine Eigenschaft einen Getter und einen Setter aufweist, müssen beide "virtual", "static" oder "instance" sein. Die C#- und Visual Basic-Compiler erzwingen diese Regel automatisch durch ihre Eigenschaftsdefinitionssyntax.
Ereignisse
Ein Ereignis wird durch seinen Namen und seinen Typ definiert. Der Ereignistyp ist ein Delegat, der zum Angeben des Ereignisses verwendet wird. Zum Beispiel hat das AppDomain.AssemblyResolve-Ereignis den Typ ResolveEventHandler. Zusätzlich zum Ereignis selbst geben drei Methoden mit Namen basierend auf dem Ereignisnamen die Implementierung des Ereignisses an und werden in den Metadaten der Assembly markiert SpecialName
:
Eine Methode zum Hinzufügen eines Ereignishandlers namens
add_
"EventName". Zum Beispiel heißt die Ereignisabonnementmethode für das AppDomain.AssemblyResolve-Ereignisadd_AssemblyResolve
.Eine Methode zum Entfernen eines Ereignishandlers namens
remove_
"EventName". Die Entfernungsmethode für das AppDomain.AssemblyResolve Ereignis wird z. B. benanntremove_AssemblyResolve
.Eine Methode, die angibt, dass das Ereignis aufgetreten ist, mit dem Namen
raise_
"EventName".
Hinweis
Die meisten Regeln der Common Language Specification bezüglich Ereignissen werden von Sprachcompilern implementiert und sind für Komponentenentwickler transparent.
Die Methoden zum Hinzufügen, Entfernen und Auslösen des Ereignisses müssen die gleiche Barrierefreiheit aufweisen. Sie müssen alle entweder statisch, eine Instanz oder virtuell sein. Die Methoden zum Hinzufügen und Entfernen eines Ereignisses weisen einen Parameter auf, dessen Typ der Ereignisdelegattyp ist. Die Methoden zum Hinzufügen und Entfernen müssen vorhanden oder beide nicht vorhanden sein.
Im folgenden Beispiel wird eine CLS-kompatible Klasse definiert Temperature
, die ein TemperatureChanged
Ereignis auslöst, wenn die Temperaturänderung zwischen zwei Werten gleich oder einen Schwellenwert überschreitet. Die Temperature
Klasse definiert explizit eine raise_TemperatureChanged
Methode, sodass sie Ereignishandler selektiv ausführen kann.
using System;
using System.Collections;
using System.Collections.Generic;
[assembly: CLSCompliant(true)]
public class TemperatureChangedEventArgs : EventArgs
{
private Decimal originalTemp;
private Decimal newTemp;
private DateTimeOffset when;
public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)
{
originalTemp = original;
newTemp = @new;
when = time;
}
public Decimal OldTemperature
{
get { return originalTemp; }
}
public Decimal CurrentTemperature
{
get { return newTemp; }
}
public DateTimeOffset Time
{
get { return when; }
}
}
public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);
public class Temperature
{
private struct TemperatureInfo
{
public Decimal Temperature;
public DateTimeOffset Recorded;
}
public event TemperatureChanged TemperatureChanged;
private Decimal previous;
private Decimal current;
private Decimal tolerance;
private List<TemperatureInfo> tis = new List<TemperatureInfo>();
public Temperature(Decimal temperature, Decimal tolerance)
{
current = temperature;
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = temperature;
tis.Add(ti);
ti.Recorded = DateTimeOffset.UtcNow;
this.tolerance = tolerance;
}
public Decimal CurrentTemperature
{
get { return current; }
set {
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = value;
ti.Recorded = DateTimeOffset.UtcNow;
previous = current;
current = value;
if (Math.Abs(current - previous) >= tolerance)
raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
}
}
public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)
{
if (TemperatureChanged == null)
return;
foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {
if (d.Method.Name.Contains("Duplicate"))
Console.WriteLine("Duplicate event handler; event handler not executed.");
else
d.Invoke(this, eventArgs);
}
}
}
public class Example
{
public Temperature temp;
public static void Main()
{
Example ex = new Example();
}
public Example()
{
temp = new Temperature(65, 3);
temp.TemperatureChanged += this.TemperatureNotification;
RecordTemperatures();
Example ex = new Example(temp);
ex.RecordTemperatures();
}
public Example(Temperature t)
{
temp = t;
RecordTemperatures();
}
public void RecordTemperatures()
{
temp.TemperatureChanged += this.DuplicateTemperatureNotification;
temp.CurrentTemperature = 66;
temp.CurrentTemperature = 63;
}
internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Notification 1: The temperature changed from {e.OldTemperature} to {e.CurrentTemperature}");
}
public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Notification 2: The temperature changed from {e.OldTemperature} to {e.CurrentTemperature}");
}
}
Imports System.Collections
Imports System.Collections.Generic
<Assembly: CLSCompliant(True)>
Public Class TemperatureChangedEventArgs : Inherits EventArgs
Private originalTemp As Decimal
Private newTemp As Decimal
Private [when] As DateTimeOffset
Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)
originalTemp = original
newTemp = [new]
[when] = [time]
End Sub
Public ReadOnly Property OldTemperature As Decimal
Get
Return originalTemp
End Get
End Property
Public ReadOnly Property CurrentTemperature As Decimal
Get
Return newTemp
End Get
End Property
Public ReadOnly Property [Time] As DateTimeOffset
Get
Return [when]
End Get
End Property
End Class
Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)
Public Class Temperature
Private Structure TemperatureInfo
Dim Temperature As Decimal
Dim Recorded As DateTimeOffset
End Structure
Public Event TemperatureChanged As TemperatureChanged
Private previous As Decimal
Private current As Decimal
Private tolerance As Decimal
Private tis As New List(Of TemperatureInfo)
Public Sub New(temperature As Decimal, tolerance As Decimal)
current = temperature
Dim ti As New TemperatureInfo()
ti.Temperature = temperature
ti.Recorded = DateTimeOffset.UtcNow
tis.Add(ti)
Me.tolerance = tolerance
End Sub
Public Property CurrentTemperature As Decimal
Get
Return current
End Get
Set
Dim ti As New TemperatureInfo
ti.Temperature = value
ti.Recorded = DateTimeOffset.UtcNow
previous = current
current = value
If Math.Abs(current - previous) >= tolerance Then
raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
End If
End Set
End Property
Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)
If TemperatureChangedEvent Is Nothing Then Exit Sub
Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()
For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
If d.Method.Name.Contains("Duplicate") Then
Console.WriteLine("Duplicate event handler; event handler not executed.")
Else
d.Invoke(Me, eventArgs)
End If
Next
End Sub
End Class
Public Class Example
Public WithEvents temp As Temperature
Public Shared Sub Main()
Dim ex As New Example()
End Sub
Public Sub New()
temp = New Temperature(65, 3)
RecordTemperatures()
Dim ex As New Example(temp)
ex.RecordTemperatures()
End Sub
Public Sub New(t As Temperature)
temp = t
RecordTemperatures()
End Sub
Public Sub RecordTemperatures()
temp.CurrentTemperature = 66
temp.CurrentTemperature = 63
End Sub
Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
Handles temp.TemperatureChanged
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
End Sub
Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _
Handles temp.TemperatureChanged
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature, e.CurrentTemperature)
End Sub
End Class
Überlädt
Die Common Language Specification erzwingt die folgenden Anforderungen für überladene Member:
Mitglieder können basierend auf der Anzahl der Parameter und dem Typ eines jeden Parameters überladen werden. Aufrufkonvention, Rückgabetyp, benutzerdefinierte Modifizierer, die auf die Methode oder den zugehörigen Parameter angewendet werden, und ob Parameter nach Wert oder Verweis übergeben werden, werden beim Unterscheiden zwischen Überladungen nicht berücksichtigt. Ein Beispiel finden Sie im Code für die Anforderung, dass Namen innerhalb eines Bereichs im Abschnitt "Benennungskonventionen " eindeutig sein müssen.
Nur Eigenschaften und Methoden können überladen werden. Felder und Ereignisse können nicht überladen werden.
Generische Methoden können basierend auf der Anzahl ihrer generischen Parameter überladen werden.
Hinweis
Die Operatoren op_Explicit
und op_Implicit
sind Ausnahmen von der Regel, dass der Rückgabewert nicht als Teil einer Methodensignatur für die Überladungsauflösung betrachtet wird. Diese beiden Operatoren können basierend auf ihren Parametern und ihrem Rückgabewert überladen werden.
Ausnahmen
Ausnahmeobjekte müssen von System.Exception oder von einem anderen Typ abgeleitet werden, der von System.Exception abgeleitet wird. Im folgenden Beispiel wird der Compilerfehler veranschaulicht, der beim Verwenden einer benutzerdefinierten Klasse mit dem Namen ErrorClass
für die Ausnahmebehandlung resultiert.
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
' ~~~~~~~~~~~~~~
Um diesen Fehler zu beheben, muss die Klasse ErrorClass
von System.Exception erben. Außerdem muss die Message
- Eigenschaft überschrieben werden. Im folgenden Beispiel werden diese Fehler korrigiert, um eine ErrorClass
Klasse zu definieren, die CLS-kompatibel ist.
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
Attribute
In .NET-Assemblys stellen benutzerdefinierte Attribute einen erweiterbaren Mechanismus zum Speichern von benutzerdefinierten Attributen und Abrufen von Metadaten zu Programmierobjekten bereit, z. B. Assemblys, Typen, Member und Methodenparameter. Benutzerdefinierte Attribute müssen von System.Attribute oder von einem Typ, der davon abgeleitet ist, abgeleitet werden System.Attribute
.
Im folgenden Beispiel wird diese Regel verletzt. Definiert eine Klasse NumericAttribute
, die nicht von System.Attribute abgeleitet ist. Ein Compilerfehler tritt nur dann auf, wenn das nicht-CLS-kompatible Attribut angewendet wird, nicht wenn die Klasse definiert ist.
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
' ~~~~~~~~~~~~~
Der Konstruktor oder die Eigenschaften eines CLS-kompatiblen Attributs können nur die folgenden Typen verfügbar machen:
Im folgenden Beispiel wird eine DescriptionAttribute
Klasse definiert, die von "Attribute" abgeleitet wird. Der Klassenkonstruktor weist einen Parameter vom Typ Descriptor
auf, sodass die Klasse nicht CLS-kompatibel ist. Der C#-Compiler gibt eine Warnung aus, kompiliert aber erfolgreich, während der Visual Basic-Compiler keine Warnung oder einen Fehler ausgibt.
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
Das CLSCompliantAttribute-Attribut
Das CLSCompliantAttribute-Attribut wird verwendet, um anzugeben, ob ein Programmelement der Common Language Specification entspricht. Der CLSCompliantAttribute(Boolean) Konstruktor enthält einen einzelnen erforderlichen Parameter isCompliant, der angibt, ob das Programmelement CLS-kompatibel ist.
Zur Kompilierzeit erkennt der Compiler nicht kompatible Elemente, von denen angenommen wird, dass sie CLS-kompatibel sind, und gibt eine Warnung aus. Der Compiler gibt keine Warnungen für Typen oder Member aus, die explizit als nicht konform deklariert wurden.
Komponentenentwickler können das CLSCompliantAttribute
Attribut auf zwei Arten verwenden:
So definieren Sie die Teile der öffentlichen Schnittstelle, die von einer Komponente verfügbar gemacht werden, die CLS-kompatibel ist, und die Teile, die nicht CLS-kompatibel sind. Wenn das Attribut verwendet wird, um bestimmte Programmelemente als CLS-kompatibel zu kennzeichnen, garantiert die Verwendung, dass diese Elemente von allen Sprachen und Tools für .NET zugänglich sind.
Um sicherzustellen, dass die öffentliche Schnittstelle der Komponentenbibliothek nur Programmelemente verfügbar macht, die CLS-kompatibel sind. Wenn Elemente nicht CLS-kompatibel sind, wird in der Regel eine Warnung ausgegeben.
Warnung
In einigen Fällen erzwingen Sprachcompiler CLS-kompatible Regeln, unabhängig davon, ob das CLSCompliantAttribute
Attribut verwendet wird. Beispielsweise verstößt das Definieren eines statischen Elements in einer Schnittstelle gegen eine CLS-Regel. Wenn Sie in diesem Zusammenhang ein static
(in C#) oder Shared
(in Visual Basic) Member in einer Schnittstelle definieren, zeigen sowohl die C#- als auch die Visual Basic-Compiler eine Fehlermeldung an, und die App kann nicht kompiliert werden.
Das CLSCompliantAttribute-Attribut ist mit einem AttributeUsageAttribute-Attribut gekennzeichnet, das einen Wert von AttributeTargets.All hat. Mit diesem Wert können Sie das CLSCompliantAttribute Attribut auf jedes Programmelement anwenden, einschließlich Assemblys, Module, Typen (Klassen, Strukturen, Enumerationen, Schnittstellen und Delegaten), Typmber (Konstruktoren, Methoden, Eigenschaften, Felder und Ereignisse), Parameter, generische Parameter und Rückgabewerte. In der Praxis sollten Sie das Attribut allerdings nur auf Assemblys, Typen und Typmember anwenden. Andernfalls ignorieren Compiler das Attribut und generieren weiterhin Compilerwarnungen, wenn sie auf einen nicht kompatiblen Parameter, generischen Parameter oder Rückgabewert in der öffentlichen Schnittstelle Ihrer Bibliothek stoßen.
Der Wert des CLSCompliantAttribute Attributs wird von enthaltenen Programmelementen geerbt. Wenn eine Assembly beispielsweise als CLS-kompatibel gekennzeichnet ist, sind die Typen auch CLS-kompatibel. Wenn ein Typ als CLS-kompatibel gekennzeichnet ist, sind seine geschachtelten Typen und Member auch CLS-kompatibel.
Sie können die geerbte Compliance explizit außer Kraft setzen, indem Sie das CLSCompliantAttribute Attribut auf ein enthaltenes Programmelement anwenden. Beispielsweise können Sie das CLSCompliantAttribute-Attribut mit einem Wert von isCompliant
false
verwenden, um einen inkompatiblen Typ in einer kompatiblen Assembly zu definieren, und Sie können das Attribut mit einem Wert von isCompliant
true
verwenden, um einen kompatiblen Typ in einer inkompatiblen Assembly zu definieren. Sie können auch nicht kompatible Member in einem kompatiblen Typ definieren. Allerdings kann ein nicht kompatibler Typ keine kompatiblen Member aufweisen, sodass Sie das Attribut nicht mit einem Wert von isCompliant
true
verwenden können, um die Vererbung eines nicht kompatiblen Typs zu überschreiben.
Wenn Sie Komponenten entwickeln, sollten Sie immer das CLSCompliantAttribute Attribut verwenden, um anzugeben, ob Ihre Assembly, ihre Typen und ihre Member CLS-kompatibel sind.
So erstellen Sie CLS-kompatible Komponenten:
Verwenden Sie CLSCompliantAttribute, um Ihre Assembly als CLS-kompatibel zu kennzeichnen.
Markieren Sie jeden öffentlich verfügbar gemachten, nicht CLS-kompatiblen Typ in der Assembly als nicht kompatibel.
Markieren Sie alle öffentlich verfügbar gemachten Member in CLS-kompatiblen Typen als nicht kompatibel.
Stellen Sie eine CLS-kompatible Alternative für nicht CLS-kompatible Mitglieder bereit.
Wenn Sie alle nicht konformen Typen und Elemente erfolgreich markiert haben, sollte der Compiler keine Warnungen zur Nichteinhaltung ausgeben. Sie sollten jedoch angeben, welche Mitglieder nicht CLS-kompatibel sind, und ihre CLS-kompatiblen Alternativen in Ihrer Produktdokumentation auflisten.
Im folgenden Beispiel wird das CLSCompliantAttribute Attribut verwendet, um eine CLS-kompatible Assembly und einen Typ zu definieren, CharacterUtilities
der zwei nicht CLS-kompatible Member enthält. Da beide Elemente mit dem CLSCompliant(false)
Attribut gekennzeichnet sind, erzeugt der Compiler keine Warnungen. Die Klasse stellt auch eine CLS-kompatible Alternative für beide Methoden bereit. Normalerweise würden der ToUTF16
- Methode lediglich zwei Überladungen hinzugefügt werden, um CLS-kompatible Alternativen bereitzustellen. Da Methoden jedoch nicht basierend auf dem Rückgabewert überladen werden können, unterscheiden sich die Namen der CLS-kompatiblen Methoden von den Namen der nicht kompatiblen Methoden.
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
Wenn Sie eine App anstelle einer Bibliothek entwickeln (d. h., wenn Sie keine Typen oder Member verfügbarmachen, die von anderen App-Entwicklern genutzt werden können), ist die CLS-Kompatibilität der von Ihrer App genutzten Programmelemente nur dann von Interesse, wenn Ihre Sprache sie nicht unterstützt. In diesem Fall generiert der Sprachcompiler einen Fehler, wenn Sie versuchen, ein nicht CLS-kompatibles Element zu verwenden.
Sprachübergreifende Interoperabilität
Die Sprachunabhängigkeit hat einige mögliche Bedeutungen. Eine Bedeutung besteht darin, Typen, die in einer Sprache verfasst wurden, nahtlos in einer App zu verwenden, die in einer anderen Sprache geschrieben ist. Eine zweite Bedeutung, die im Mittelpunkt dieses Artikels steht, umfasst die Kombination von Code, der in mehreren Sprachen in einer einzigen .NET-Assembly geschrieben wurde.
Im folgenden Beispiel wird die sprachübergreifende Interoperabilität veranschaulicht, indem eine Klassenbibliothek mit dem Namen Utilities.dll erstellt wird, die zwei Klassen enthält, NumericLib
und StringLib
. Die NumericLib
Klasse wird in C# geschrieben, und die StringLib
Klasse wird in Visual Basic geschrieben. Hier sehen Sie den Quellcode für StringUtil.vb
, der ein einzelnes Element ToTitleCase
in seiner StringLib
Klasse enthält.
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
Dies ist der Quellcode für NumberUtil.cs, der eine NumericLib
Klasse definiert, die zwei Member enthält, IsEven
und NearZero
.
using System;
public static class NumericLib
{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return Convert.ToInt64(number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) % 2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}
public static bool NearZero(double number)
{
return Math.Abs(number) < .00001;
}
}
Um die beiden Klassen in einer einzigen Assembly zu verpacken, müssen Sie sie in Module kompilieren. Verwenden Sie den folgenden Befehl, um die Visual Basic-Quellcodedatei in ein Modul zu kompilieren:
vbc /t:module StringUtil.vb
Weitere Informationen zur Befehlszeilensyntax des Visual Basic-Compilers finden Sie unter Building from the Command Line.
Verwenden Sie den folgenden Befehl, um die C#-Quellcodedatei in ein Modul zu kompilieren:
csc /t:module NumberUtil.cs
Anschließend verwenden Sie die Linker-Optionen , um die beiden Module in einer Assembly zu kompilieren:
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
Im folgenden Beispiel werden dann die Methoden NumericLib.NearZero
und StringLib.ToTitleCase
aufgerufen. Sowohl der Visual Basic-Code als auch der C#-Code können auf die Methoden in beiden Klassen zugreifen.
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
Verwenden Sie den folgenden Befehl, um den Visual Basic-Code zu kompilieren:
vbc example.vb /r:UtilityLib.dll
Um mit C# zu kompilieren, ändern Sie den Namen des Compilers von "vbc
" in "csc
" und ändern Sie die Dateierweiterung von .vb in .cs.
csc example.cs /r:UtilityLib.dll