Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
.NET è indipendente dal linguaggio. Ciò significa che, in qualità di sviluppatore, è possibile sviluppare in uno dei molti linguaggi destinati alle implementazioni .NET, ad esempio C#, F# e Visual Basic. È possibile accedere ai tipi e ai membri delle librerie di classi sviluppate per le implementazioni .NET senza dover conoscere il linguaggio in cui sono stati originariamente scritti e senza dover seguire alcuna delle convenzioni del linguaggio originale. Gli sviluppatori di componenti possono accedere a qualsiasi app .NET, indipendentemente dal linguaggio.
Annotazioni
Questa prima parte di questo articolo illustra la creazione di componenti indipendenti dal linguaggio, ovvero componenti che possono essere utilizzati dalle app scritte in qualsiasi linguaggio. È anche possibile creare un singolo componente o un'app dal codice sorgente scritto in più lingue; vedere Interoperabilità tra linguaggi nella seconda parte di questo articolo.
Per interagire completamente con altri oggetti scritti in qualsiasi linguaggio, gli oggetti devono esporre ai chiamanti solo le funzionalità comuni a tutti i linguaggi. Questo set comune di funzionalità è definito da Common Language Specification (CLS), ovvero un set di regole che si applicano agli assembly generati. Le Specifiche del Linguaggio Comune sono definite nella Partizione I, dalle clausole 7 a 11 dello standard ECMA-335: Common Language Infrastructure.
Se il componente è conforme a Common Language Specification, è garantito che sia conforme a CLS e accessibile dal codice negli assembly scritti in qualsiasi linguaggio di programmazione che supporti CLS. È possibile determinare se il componente è conforme a Common Language Specification in fase di compilazione applicando l'attributo CLSCompliantAttribute al codice sorgente. Per altre informazioni, vedere Attributo CLSCompliantAttribute.
Regole di conformità CLS
Questa sezione illustra le regole per la creazione di un componente conforme a CLS. Per un elenco completo delle regole, vedere Partizione I, clausola 11 dello standard ECMA-335: Common Language Infrastructure.
Annotazioni
La specifica common language illustra ogni regola per la conformità a CLS in quanto si applica ai consumer (sviluppatori che accedono a livello di codice a un componente conforme a CLS), framework (sviluppatori che usano un compilatore di linguaggio per creare librerie conformi a CLS) ed extender (sviluppatori che creano uno strumento come un compilatore di linguaggio o un parser di codice che crea componenti conformi a CLS). Questo articolo è incentrato sulle regole applicate ai framework. Si noti, tuttavia, che alcune delle regole applicabili agli extender possono essere applicate anche agli assembly creati tramite Reflection.Emit.
Per progettare un componente indipendente dal linguaggio, è sufficiente applicare le regole per la conformità a CLS all'interfaccia pubblica del componente. L'implementazione privata non deve essere conforme alla specifica.
Importante
Le regole per la conformità a CLS si applicano solo all'interfaccia pubblica di un componente, non all'implementazione privata.
Ad esempio, interi senza segno diversi da Byte non sono conformi a CLS. Poiché la Person
classe nell'esempio seguente espone una Age
proprietà di tipo UInt16, il codice seguente visualizza un avviso del compilatore.
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
' ~~~
È possibile rendere la classe Person
conforme a CLS modificando il tipo della proprietà Age
da UInt16 a Int16, un intero con segno a 16 bit conforme al CLS. Non è necessario modificare il tipo del campo privato personAge
.
using System;
[assembly: CLSCompliant(true)]
public class Person
{
private Int16 personAge = 0;
public Int16 Age
{ get { return personAge; } }
}
<Assembly: CLSCompliant(True)>
Public Class Person
Private personAge As UInt16
Public ReadOnly Property Age As Int16
Get
Return CType(personAge, Int16)
End Get
End Property
End Class
L'interfaccia pubblica di una libreria è costituita dai seguenti elementi:
Definizioni delle classi pubbliche.
Definizioni dei membri pubblici delle classi pubbliche e definizioni dei membri accessibili alle classi derivate ( ovvero i membri protetti).
Parametri e tipi restituiti di metodi pubblici di classi pubbliche e parametri e tipi restituiti di metodi accessibili alle classi derivate.
Le regole per la conformità a CLS sono elencate nella tabella seguente. Il testo delle regole viene preso verbatim dallo standard ECMA-335: Common Language Infrastructure, che è Copyright 2012 di Ecma International. Le informazioni più dettagliate su queste regole sono disponibili nelle sezioni seguenti.
Categoria | Visualizza | Regola | Numero regola |
---|---|---|---|
Accessibilità | Accessibilità dei membri | L'accessibilità non deve essere modificata quando si esegue l'override dei metodi ereditati, tranne quando si esegue l'override di un metodo ereditato da un assembly diverso con accessibilità family-or-assembly . In questo caso, l'override avrà accessibilità family . |
10 |
Accessibilità | Accessibilità dei membri | La visibilità e l'accessibilità di tipi e membri devono essere tali che i tipi nella firma di qualsiasi membro siano visibili e accessibili ogni volta che il membro stesso è visibile e accessibile. Ad esempio, un metodo pubblico visibile all'esterno dell'assembly non deve avere un argomento il cui tipo è visibile solo all'interno dell'assembly. La visibilità e l'accessibilità dei tipi che compongono un tipo generico di cui è stata creata un'istanza usata nella firma di qualsiasi membro devono essere visibili e accessibili ogni volta che il membro stesso è visibile e accessibile. Ad esempio, un tipo generico di cui è stata creata un'istanza presente nella firma di un membro visibile all'esterno dell'assembly non deve avere un argomento generico il cui tipo è visibile solo all'interno dell'assembly. | 12 |
Matrici | matrici | Le matrici devono avere elementi con un tipo conforme a CLS e tutte le dimensioni della matrice devono avere limiti inferiori pari a zero. Solo il fatto che un elemento sia un array e il tipo di elemento dell'array sono necessari per distinguere tra i sovraccarichi. Quando l'overload è basato su due o più tipi di matrice, i tipi di elemento devono essere tipi con nomi. | 16 |
Attributi | Attributi | Gli attributi devono essere di tipo System.Attributeo un tipo che eredita da esso. | 41 |
Attributi | Attributi | CLS consente solo un subset delle codifiche degli attributi personalizzati. Gli unici tipi che devono essere visualizzati in queste codifiche sono (vedere Partition IV): System.Type, System.StringSystem.CharSystem.BooleanSystem.Byte, System.Int16, System.Int32System.Int64System.Single, System.Double, , e qualsiasi tipo di enumerazione basato su un tipo integer di base conforme a CLS. | 34 |
Attributi | Attributi | CLS non consente modificatori obbligatori visibili pubblicamente (modreq vedere Partizione II), ma consente modificatori facoltativi (modopt vedere Partizione II) che non riconosce. |
35 |
Costruttori | Costruttori | Un costruttore di oggetti deve chiamare un costruttore di istanza della relativa classe di base prima che si verifichi qualsiasi accesso ai dati dell'istanza ereditata. Questo non si applica ai tipi valore, che non necessitano di costruttori. | 21 |
Costruttori | Costruttori | Un costruttore di oggetti non deve essere chiamato tranne nell'ambito della creazione di un oggetto e un oggetto non deve essere inizializzato due volte. | 22 |
Enumerazioni | Enumerazioni | Il tipo sottostante di un'enumerazione deve essere un tipo intero CLS predefinito, il nome del campo deve essere "value__" e tale campo deve essere contrassegnato come RTSpecialName . |
7 |
Enumerazioni | Enumerazioni | Esistono due tipi distinti di enumerazioni, indicate dalla presenza o dall'assenza dell'attributo System.FlagsAttribute personalizzato (vedere Partition IV Library). Uno rappresenta i valori integer denominati; l'altro rappresenta flag di bit denominati che possono essere combinati per generare un valore senza nome. Il valore di un oggetto enum non è limitato ai valori specificati. |
8 |
Enumerazioni | Enumerazioni | I campi statici letterali di un'enumerazione devono avere il tipo dell'enumerazione stessa. | 9 |
Avvenimenti | Eventi | I metodi che implementano un evento devono essere contrassegnati SpecialName nei metadati. |
29 |
Avvenimenti | Eventi | L'accessibilità di un evento e delle relative funzioni di accesso deve essere identica. | 30 |
Avvenimenti | Eventi | I add metodi e remove per un evento devono essere presenti o assenti. |
31 |
Avvenimenti | Eventi | I add metodi e remove per un evento accettano ogni parametro il cui tipo definisce il tipo dell'evento e che deve essere derivato da System.Delegate. |
32 |
Avvenimenti | Eventi | Gli eventi devono rispettare un modello di denominazione specifico. L'attributo SpecialName a cui fa riferimento nella regola CLS 29 deve essere ignorato nei confronti dei nomi appropriati e deve rispettare le regole di identificatore. | 33 |
Eccezioni | Eccezioni | Gli oggetti generati devono appartenere al tipo System.Exception o a un tipo che eredita da esso. Tuttavia, i metodi conformi a CLS non sono necessari per bloccare la propagazione di altri tipi di eccezioni. | 40 |
Generale | Regole di conformità CLS | Le regole CLS si applicano solo a quelle parti di un tipo accessibili o visibili all'esterno dell'assembly di definizione. | 1 |
Generale | Regole di conformità CLS | I membri di tipi non conformi a CLS non devono essere contrassegnati come conformi a CLS. | 2 |
Generici | Tipi e membri generici | I tipi annidati devono avere almeno il numero di parametri generici del tipo di inclusione. I parametri generici in un tipo annidato corrispondono in base alla posizione ai parametri generici nel relativo tipo contenitore. | 42 |
Generici | Tipi e membri generici | Il nome di un tipo generico codifica il numero di parametri di tipo dichiarati nel tipo non annidato o appena introdotto nel tipo se annidato, in base alle regole definite in precedenza. | 43 |
Generici | Tipi e membri generici | Un tipo generico deve ridefinire vincoli sufficienti per garantire che qualsiasi vincolo sul tipo di base o sulle interfacce sia soddisfatto dai vincoli di tipo generico. | 44 |
Generici | Tipi e membri generici | I tipi usati come vincoli per i parametri generici devono essere conformi a CLS. | 45 |
Generici | Tipi e membri generici | La visibilità e l'accessibilità dei membri (inclusi i tipi annidati) in un tipo generico istanziato devono essere considerate come limitate alla specifica istanza piuttosto che alla dichiarazione del tipo generico nel suo insieme. Presumendo questo, le regole di visibilità e accessibilità della CLS 12 sono ancora in vigore. | 46 |
Generici | Tipi e membri generici | Per ogni metodo generico astratto o virtuale, deve esserci un'implementazione concreta predefinita (non astratta) | 47 |
Interfacce | Interfacce | Le interfacce conformi a CLS non richiedono la definizione di metodi non conformi a CLS per implementarli. | 18 |
Interfacce | Interfacce | Le interfacce conformi a CLS non definiscono metodi statici, né definiscono i campi. | 19 |
Membri | Membri di tipo in generale | I campi e i metodi statici globali non sono conformi a CLS. | 36 |
Membri | -- | Il valore di un valore letterale statico viene specificato utilizzando i metadati di inizializzazione dei campi. Un valore letterale conforme a CLS deve avere un valore specificato nei metadati di inizializzazione dei campi che è esattamente dello stesso tipo del valore letterale (o del tipo sottostante, se tale valore letterale è ).enum |
13 |
Membri | Membri di tipo in generale | Il vincolo vararg non fa parte di CLS e l'unica convenzione di chiamata supportata da CLS è la convenzione di chiamata gestita standard. | 15 |
Convenzioni di denominazione | Convenzioni di denominazione | Gli assembly seguiranno l'allegato 7 della relazione tecnica 15 dello standard Unicode 3.0 che regola il set di caratteri consentiti per l'avvio e l'inserimento negli identificatori, disponibili online in Unicode Normalization Forms. Gli identificatori devono essere nel formato canonico definito dalla Formattazione Unicode di Normalizzazione C. Ai fini di CLS, due identificatori sono considerati identici se le loro mappature in minuscolo (come specificato dalle mappature minuscole uno-a-uno indipendenti dalla localizzazione Unicode) sono identiche. In altre parole, perché due identificatori siano considerati diversi sotto il CLS, devono differire in più che il semplice caso. Tuttavia, per eseguire l'override di una definizione ereditata, l'interfaccia della riga di comando richiede l'uso della codifica precisa della dichiarazione originale. | 4 |
Sovraccarico | Convenzioni di denominazione | Tutti i nomi introdotti in un ambito conforme a CLS devono essere distinti indipendentemente dalla categoria, ad eccezione dei casi in cui i nomi sono identici e risolti tramite il meccanismo di overloading. Ovvero, mentre CTS consente a un singolo tipo di usare lo stesso nome per un metodo e un campo, CLS non lo fa. | 5 |
Sovraccarico | Convenzioni di denominazione | I campi e i tipi annidati devono essere distinti in base al solo confronto degli identificatori, anche se il CTS consente di distinguere le firme distinte. I metodi, le proprietà e gli eventi con lo stesso nome (in base al confronto degli identificatori) differiscono per più del tipo restituito, ad eccezione di quanto specificato nella regola CLS 39 | 6 |
Sovraccarico | Sovraccarichi | È possibile eseguire l'overload soltanto di proprietà e metodi. | 37 |
Sovraccarico | Sovraccarichi | Le proprietà e i metodi possono essere sovraccaricati solo in base al numero e ai tipi dei relativi parametri, ad eccezione degli operatori di conversione denominati op_Implicit e op_Explicit , che possono anche essere sovraccaricati in base al tipo restituito. |
38 |
Sovraccarico | -- | Se due o più metodi conformi a CLS dichiarati in un tipo hanno lo stesso nome e, per un set specifico di istanze di tipo, hanno gli stessi parametri e tipi restituiti, tutti questi metodi devono essere semanticamente equivalenti a tali istanze di tipo. | 48 |
Proprietà | Proprietà | I metodi che implementano i metodi getter e setter di una proprietà devono essere contrassegnati SpecialName nei metadati. |
24 |
Proprietà | Proprietà | Le funzioni di accesso di una proprietà devono essere tutte statiche, tutte virtuali o tutte come istanze. | 26 |
Proprietà | Proprietà | Il tipo di una proprietà deve essere il tipo restituito del getter e il tipo dell'ultimo argomento del setter. I tipi dei parametri della proprietà devono essere i tipi dei parametri per il getter e i tipi di tutti i parametri, ma il parametro finale del setter. Tutti questi tipi devono essere conformi a CLS e non devono essere puntatori gestiti , ovvero non devono essere passati per riferimento. | 27 |
Proprietà | Proprietà | Le proprietà devono essere conformi a un modello di denominazione specifico. L'attributo SpecialName menzionato nella regola CLS 24 deve essere ignorato durante i confronti di nomi appropriati e deve aderire alle regole degli identificatori. Una proprietà deve avere un metodo getter, un metodo setter o entrambi. |
28 |
Conversione di tipo | Conversione di tipi | Se viene fornito op_Implicit o op_Explicit, verrà fornito un mezzo alternativo per fornire la coercizione. | 39 |
Tipi | Tipi e firme dei membri di tipo | I tipi valore boxed non sono conformi a CLS. | 3 |
Tipi | Tipi e firme dei membri di tipo | Tutti i tipi presenti in una firma devono essere conformi a CLS. Tutti i tipi che compongono un tipo generico di cui è stata creata un'istanza devono essere compatibili con le specifiche CLS. | 11 |
Tipi | Tipi e firme dei membri di tipo | I riferimenti tipizzati non sono conformi a CLS. | 14 |
Tipi | Tipi e firme dei membri di tipo | I tipi di puntatore non gestiti non sono conformi a CLS. | 17 |
Tipi | Tipi e firme dei membri di tipo | Le classi conformi a CLS, i tipi valore e le interfacce non richiedono l'implementazione di membri non conformi a CLS | 20 |
Tipi | Tipi e firme dei membri di tipo | System.Object è conforme a CLS. Qualsiasi altra classe conforme a CLS eredita da una classe conforme a CLS. | 23 |
Indice a sottosezioni:
- Tipi e firme dei membri di tipo
- Convenzioni di denominazione
- Conversione di tipi
- matrici
- Interfacce
- Enumerazioni
- Membri di tipo in generale
- Accessibilità dei membri
- Tipi e membri generici
- Costruttori
- Proprietà
- Eventi
- Sovraccarichi
- Eccezioni
- Attributi
Tipi e firme dei membri di tipo
Il tipo System.Object è conforme a CLS ed è il tipo di base di tutti i tipi di oggetto nel sistema di tipi .NET. L'ereditarietà in .NET è implicita( ad esempio, la classe String eredita in modo implicito dalla Object
classe ) o esplicita (ad esempio, la classe CultureNotFoundException eredita in modo esplicito dalla classe ArgumentException , che eredita in modo esplicito dalla classe Exception . Affinché un tipo derivato sia conforme a CLS, anche il tipo di base deve essere conforme a CLS.
Nell'esempio seguente viene illustrato un tipo derivato il cui tipo di base non è conforme a CLS. Definisce una classe base Counter
che usa un intero senza segno a 32 bit come contatore. Poiché la classe fornisce la funzionalità di contatore racchiudendo un intero senza segno, la classe viene contrassegnata come non conforme alle specifiche CLS. Di conseguenza, una classe derivata, NonZeroCounter
, non è conforme a CLS.
using System;
[assembly: CLSCompliant(true)]
[CLSCompliant(false)]
public class Counter
{
UInt32 ctr;
public Counter()
{
ctr = 0;
}
protected Counter(UInt32 ctr)
{
this.ctr = ctr;
}
public override string ToString()
{
return String.Format("{0}). ", ctr);
}
public UInt32 Value
{
get { return ctr; }
}
public void Increment()
{
ctr += (uint) 1;
}
}
public class NonZeroCounter : Counter
{
public NonZeroCounter(int startIndex) : this((uint) startIndex)
{
}
private NonZeroCounter(UInt32 startIndex) : base(startIndex)
{
}
}
// Compilation produces a compiler warning like the following:
// Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
// CLS-compliant
// Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
<CLSCompliant(False)> _
Public Class Counter
Dim ctr As UInt32
Public Sub New
ctr = 0
End Sub
Protected Sub New(ctr As UInt32)
ctr = ctr
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0}). ", ctr)
End Function
Public ReadOnly Property Value As UInt32
Get
Return ctr
End Get
End Property
Public Sub Increment()
ctr += CType(1, UInt32)
End Sub
End Class
Public Class NonZeroCounter : Inherits Counter
Public Sub New(startIndex As Integer)
MyClass.New(CType(startIndex, UInt32))
End Sub
Private Sub New(startIndex As UInt32)
MyBase.New(CType(startIndex, UInt32))
End Sub
End Class
' Compilation produces a compiler warning like the following:
' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
' because it derives from 'Counter', which is not CLS-compliant.
'
' Public Class NonZeroCounter : Inherits Counter
' ~~~~~~~~~~~~~~
Tutti i tipi visualizzati nelle firme dei membri, incluso il tipo restituito di un metodo o un tipo di proprietà, devono essere conformi alla specifica CLS (Common Language Specification). Inoltre, per i tipi generici:
Tutti i tipi che compongono un tipo generico istanziato devono essere conformi alle specifiche CLS (Common Language Specification).
Tutti i tipi usati come vincoli per i parametri generici devono essere conformi a CLS.
Il sistema di tipi comuni .NET include molti tipi predefiniti supportati direttamente da Common Language Runtime e sono codificati in modo speciale nei metadati di un assembly. Di questi tipi intrinseci, i tipi elencati nella tabella seguente sono conformi a CLS.
Tipo conforme a CLS | Descrizione |
---|---|
Byte | Intero senza segno a 8 bit |
Int16 | Intero con segno a 16 bit |
Int32 | Intero con segno a 32 bit |
Int64 | Intero con segno a 64 bit |
Metà | Valore a virgola mobile a mezza precisione |
Singolo | Valore a virgola mobile a precisione singola |
Doppio | Valore a virgola mobile a doppia precisione |
Booleano | tipo di valore true o false |
Char | Unità di codice con codifica UTF-16 |
Decimale | Numero decimale non a virgola mobile |
IntPtr | Puntatore o handle di dimensioni definite dalla piattaforma |
Stringa | Raccolta di zero, uno o più oggetti Char |
I tipi intrinseci elencati nella tabella seguente non sono conformi a CLS.
Tipo non conforme | Descrizione | Alternativa conforme a CLS |
---|---|---|
SByte | Tipo di dati intero con segno a 8 bit | Int16 |
UInt16 | Intero senza segno a 16 bit | Int32 |
UInt32 | Intero senza segno a 32 bit | Int64 |
UInt64 | Intero senza segno a 64 bit | Int64 (può eseguire l'overflow), BigInteger o Double |
UIntPtr | Puntatore o handle non firmato | IntPtr |
La libreria di classi .NET o qualsiasi altra libreria di classi può includere altri tipi non conformi a CLS, ad esempio:
Tipi valore boxed. Nell'esempio C# seguente viene creata una classe con una proprietà pubblica di tipo
int*
denominataValue
. Poiché èint*
un tipo di valore boxed, il compilatore lo contrassegna come non conforme a CLS.using System; [assembly:CLSCompliant(true)] public unsafe class TestClass { private int* val; public TestClass(int number) { val = (int*) number; } public int* Value { get { return val; } } } // The compiler generates the following output when compiling this example: // warning CS3003: Type of 'TestClass.Value' is not CLS-compliant
Riferimenti tipizzati, che sono costrutti speciali che contengono un riferimento a un oggetto e un riferimento a un tipo. I riferimenti tipizzati sono rappresentati in .NET dalla classe TypedReference.
Se un tipo non è conforme a CLS, è necessario applicare a esso l'attributo CLSCompliantAttribute con un valore isCompliant
di false
. Per altre informazioni, vedere la sezione Attributo CLSCompliantAttribute .
Nell'esempio seguente viene illustrato il problema della conformità CLS in una firma di metodo e nell'instanziazione di tipi generici. Definisce una InvoiceItem
classe con una proprietà di tipo UInt32, una proprietà di tipo Nullable<UInt32>
e un costruttore con parametri di tipo UInt32 e Nullable<UInt32>
. Quando si tenta di compilare questo esempio, vengono visualizzati quattro avvisi del compilatore.
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
' ~~~~~~~~~
Per eliminare gli avvisi del compilatore, sostituire i tipi non conformi a CLS nell'interfaccia InvoiceItem
pubblica con tipi conformi:
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
Oltre ai tipi specifici elencati, alcune categorie di tipi non sono conformi a CLS. Questi includono tipi di puntatore non gestiti e tipi di puntatore a funzione. Nell'esempio seguente viene generato un avviso del compilatore perché usa un puntatore a un numero intero per creare una matrice di numeri interi.
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
Per le classi astratte conformi a CLS( ovvero le classi contrassegnate come abstract
in C# o come MustInherit
in Visual Basic), tutti i membri della classe devono essere conformi a CLS.
Convenzioni di denominazione
Poiché alcuni linguaggi di programmazione non sono sensibili alle maiuscole e minuscole, gli identificatori (ad esempio i nomi di namespace, tipi e membri) devono differire in modo più significativo delle sole maiuscole e minuscole. Due identificatori sono considerati equivalenti se le loro conversioni in minuscolo sono uguali. L'esempio C# seguente definisce due classi pubbliche e Person
person
. Poiché differiscono solo per caso, il compilatore C# li contrassegna come non conformi a CLS.
using System;
[assembly: CLSCompliant(true)]
public class Person : person
{
}
public class person
{
}
// Compilation produces a compiler warning like the following:
// Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
// only in case is not CLS-compliant
// Naming1.cs(6,14): (Location of symbol related to previous warning)
Gli identificatori del linguaggio di programmazione, come i nomi di namespace, tipi e membri, devono essere conformi allo Standard Unicode. Ciò significa che:
Il primo carattere di un identificatore può essere qualsiasi lettera maiuscola Unicode, lettera minuscola, lettera del titolo, lettera modificatore, altra lettera o numero di lettera. Per informazioni sulle categorie di caratteri Unicode, vedere l'enumerazione System.Globalization.UnicodeCategory .
I caratteri successivi possono essere provenienti da qualsiasi categoria come primo carattere e possono includere anche segni di non spaziatura, spaziatura combinata di segni, numeri decimali, punteggiatura connettore e codici di formattazione.
Prima di confrontare gli identificatori, è necessario filtrare i codici di formattazione e convertire gli identificatori nel formato di normalizzazione Unicode C, perché un singolo carattere può essere rappresentato da più unità di codice con codifica UTF-16. Le sequenze di caratteri che producono le stesse unità di codice nel formato di normalizzazione Unicode C non sono conformi a CLS. Nell'esempio seguente viene definita una proprietà denominata Å
, costituita dal carattere ANGSTROM SIGN (U+212B) e da una seconda proprietà denominata Å
, costituita dal carattere LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5). I compilatori C# e Visual Basic contrassegnano il codice sorgente come non conformi a CLS.
public class Size
{
private double a1;
private double a2;
public double Å
{
get { return a1; }
set { a1 = value; }
}
public double Å
{
get { return a2; }
set { a2 = value; }
}
}
// Compilation produces a compiler warning like the following:
// Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
// CLS-compliant
// Naming2a.cs(10,18): (Location of symbol related to previous warning)
// Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
// CLS-compliant
// Naming2a.cs(12,8): (Location of symbol related to previous warning)
// Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
// CLS-compliant
// Naming2a.cs(13,8): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
Public Class Size
Private a1 As Double
Private a2 As Double
Public Property Å As Double
Get
Return a1
End Get
Set
a1 = value
End Set
End Property
Public Property Å As Double
Get
Return a2
End Get
Set
a2 = value
End Set
End Property
End Class
' Compilation produces a compiler warning like the following:
' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
' with identical signatures.
'
' Public Property Å As Double
' ~
I nomi dei membri all'interno di un determinato ambito, ad esempio gli spazi dei nomi all'interno di un assembly, i tipi all'interno di uno spazio dei nomi o i membri all'interno di un tipo, devono essere univoci, ad eccezione dei nomi risolti tramite l'overload. Questo requisito è più rigoroso rispetto a quello del sistema di tipi comune, che consente a più membri all'interno di un ambito di avere nomi identici purché siano diversi tipi di membri (ad esempio, uno è un metodo e uno è un campo). In particolare, per i membri di tipo:
I campi e i tipi annidati sono distinti esclusivamente dal nome.
I metodi, le proprietà e gli eventi con lo stesso nome devono essere diversi da quelli restituiti.
Nell'esempio seguente viene illustrato il requisito che i nomi dei membri devono essere univoci all'interno dell'ambito. Definisce una classe denominata Converter
che include quattro membri denominati Conversion
. Tre sono metodi e uno è una proprietà. Il metodo che include un Int64 parametro è denominato in modo univoco, ma i due metodi con un Int32 parametro non sono, perché il valore restituito non viene considerato parte della firma di un membro. La Conversion
proprietà viola anche questo requisito, perché le proprietà non possono avere lo stesso nome dei metodi di overload.
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
' ~~~~~~~~~~
I singoli linguaggi includono parole chiave univoche, pertanto i linguaggi destinati a Common Language Runtime devono fornire anche un meccanismo per fare riferimento agli identificatori (ad esempio i nomi dei tipi) che coincidono con le parole chiave. Ad esempio, case
è una parola chiave sia in C# che in Visual Basic. Tuttavia, l'esempio di Visual Basic seguente è in grado di disambiguare una classe denominata case
dalle parole chiave case
utilizzando parentesi graffe di apertura e chiusura. In caso contrario, l'esempio genera il messaggio di errore "Keyword is not valid as an identifier" (Parola chiave non valida come identificatore) e non viene compilata.
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
L'esempio C# seguente è in grado di creare un'istanza della classe case
usando il simbolo @
per disambiguare l'identificatore dalla parola chiave del linguaggio. Senza di esso, il compilatore C# visualizzerebbe due messaggi di errore, "Tipo previsto" e "Termine di espressione non valido 'case'".
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
Conversione di tipo
Common Language Specification definisce due operatori di conversione:
op_Implicit
, usato per le conversioni di tipo widening che non comportano la perdita di dati o precisione. Ad esempio, la struttura Decimal include un operatore sovraccaricoop_Implicit
per convertire i valori di tipi integrali e i valori di tipo Char in valori di tipo Decimal.op_Explicit
, che viene usato per le conversioni riduttive che possono comportare una perdita di grandezza (un valore viene convertito in un valore con un intervallo più piccolo) o precisione. Ad esempio, la struttura Decimal include un operatoreop_Explicit
sovraccaricato per convertire i valori Double e Single in Decimal, e per convertire i valori Decimal in valori interi, Double, Single, e Char.
Tuttavia, non tutti i linguaggi supportano l'overload degli operatori o la definizione di operatori personalizzati. Se si sceglie di implementare questi operatori di conversione, è anche necessario fornire un modo alternativo per eseguire la conversione. È consigliabile specificare From
i metodi Xxx e To
Xxx.
Nell'esempio seguente vengono definite conversioni implicite ed esplicite conformi a CLS. Crea una classe UDouble
che rappresenta un numero a virgola mobile di doppia precisione e senza segno. Fornisce conversioni implicite da UDouble
a Double e per conversioni esplicite da UDouble
a Single, Double a UDouble
e Single a UDouble
. Definisce anche un ToDouble
metodo come alternativa all'operatore di conversione implicita e ai ToSingle
metodi , FromDouble
e FromSingle
come alternative agli operatori di conversione espliciti.
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
Matrici
Le matrici conformi a CLS sono conformi alle regole seguenti:
Tutte le dimensioni di una matrice devono avere un limite inferiore pari a zero. Nell'esempio seguente viene creata una matrice non conforme a CLS con un limite inferiore di uno. Nonostante la presenza dell'attributo CLSCompliantAttribute , il compilatore non rileva che la matrice restituita dal
Numbers.GetTenPrimes
metodo non è conforme a CLS.[assembly: CLSCompliant(true)] public class Numbers { public static Array GetTenPrimes() { Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1}); arr.SetValue(1, 1); arr.SetValue(2, 2); arr.SetValue(3, 3); arr.SetValue(5, 4); arr.SetValue(7, 5); arr.SetValue(11, 6); arr.SetValue(13, 7); arr.SetValue(17, 8); arr.SetValue(19, 9); arr.SetValue(23, 10); return arr; } }
<Assembly: CLSCompliant(True)> Public Class Numbers Public Shared Function GetTenPrimes() As Array Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1}) arr.SetValue(1, 1) arr.SetValue(2, 2) arr.SetValue(3, 3) arr.SetValue(5, 4) arr.SetValue(7, 5) arr.SetValue(11, 6) arr.SetValue(13, 7) arr.SetValue(17, 8) arr.SetValue(19, 9) arr.SetValue(23, 10) Return arr End Function End Class
Tutti gli elementi della matrice devono essere costituiti da tipi conformi a CLS. Nell'esempio seguente vengono definiti due metodi che restituiscono matrici non conformi a CLS. Il primo restituisce una matrice di UInt32 valori. Il secondo restituisce un array Object che include i valori Int32 e UInt32. Anche se il compilatore identifica la prima matrice come non conforme a causa del relativo UInt32 tipo, non riconosce che la seconda matrice include elementi non conformi a CLS.
using System; [assembly: CLSCompliant(true)] public class Numbers { public static UInt32[] GetTenPrimes() { uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u }; return arr; } public static Object[] GetFivePrimes() { Object[] arr = { 1, 2, 3, 5u, 7u }; return arr; } } // Compilation produces a compiler warning like the following: // Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not // CLS-compliant
<Assembly: CLSCompliant(True)> Public Class Numbers Public Shared Function GetTenPrimes() As UInt32() Return {1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui} End Function Public Shared Function GetFivePrimes() As Object() Dim arr() As Object = {1, 2, 3, 5ui, 7ui} Return arr End Function End Class ' Compilation produces a compiler warning like the following: ' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant. ' ' Public Shared Function GetTenPrimes() As UInt32() ' ~~~~~~~~~~~~
La risoluzione dell'overload per i metodi con parametri di matrice si basa sul fatto che sono matrici e sul relativo tipo di elemento. Per questo motivo, la definizione seguente di un metodo sovraccarico
GetSquares
è conforme alle specifiche CLS.using System; using System.Numerics; [assembly: CLSCompliant(true)] public class Numbers { public static byte[] GetSquares(byte[] numbers) { byte[] numbersOut = new byte[numbers.Length]; for (int ctr = 0; ctr < numbers.Length; ctr++) { int square = ((int) numbers[ctr]) * ((int) numbers[ctr]); if (square <= Byte.MaxValue) numbersOut[ctr] = (byte) square; // If there's an overflow, assign MaxValue to the corresponding // element. else numbersOut[ctr] = Byte.MaxValue; } return numbersOut; } public static BigInteger[] GetSquares(BigInteger[] numbers) { BigInteger[] numbersOut = new BigInteger[numbers.Length]; for (int ctr = 0; ctr < numbers.Length; ctr++) numbersOut[ctr] = numbers[ctr] * numbers[ctr]; return numbersOut; } }
Imports System.Numerics <Assembly: CLSCompliant(True)> Public Module Numbers Public Function GetSquares(numbers As Byte()) As Byte() Dim numbersOut(numbers.Length - 1) As Byte For ctr As Integer = 0 To numbers.Length - 1 Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr))) If square <= Byte.MaxValue Then numbersOut(ctr) = CByte(square) ' If there's an overflow, assign MaxValue to the corresponding ' element. Else numbersOut(ctr) = Byte.MaxValue End If Next Return numbersOut End Function Public Function GetSquares(numbers As BigInteger()) As BigInteger() Dim numbersOut(numbers.Length - 1) As BigInteger For ctr As Integer = 0 To numbers.Length - 1 numbersOut(ctr) = numbers(ctr) * numbers(ctr) Next Return numbersOut End Function End Module
Interfacce
Le interfacce conformi a CLS possono definire proprietà, eventi e metodi virtuali (metodi senza implementazione). Un'interfaccia conforme a CLS non può avere uno dei seguenti elementi:
Metodi statici o campi statici. I compilatori C# e Visual Basic generano errori del compilatore se si definisce un membro statico in un'interfaccia.
Campi. I compilatori C# e Visual Basic generano errori del compilatore se si definisce un campo in un'interfaccia.
Metodi non conformi a CLS. Ad esempio, la definizione di interfaccia seguente include un metodo,
INumber.GetUnsigned
, contrassegnato come non conforme a CLS. In questo esempio viene generato un avviso del compilatore.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 ' ~~~~~~~~~~~
A causa di questa regola, i tipi conformi a CLS non sono tenuti a implementare membri non conformi a CLS. Se un framework conforme a CLS espone una classe che implementa un'interfaccia non conforme a CLS, deve anche fornire implementazioni concrete di tutti i membri non conformi a CLS.
I compilatori del linguaggio conformi a CLS devono anche consentire a una classe di fornire implementazioni separate di membri con lo stesso nome e firma in più interfacce. Sia C# che Visual Basic supportano implementazioni esplicite dell'interfaccia per fornire implementazioni diverse di metodi denominati in modo identico. Visual Basic supporta anche la Implements
parola chiave , che consente di designare in modo esplicito quale interfaccia e membro implementa un determinato membro. Nell'esempio seguente viene illustrato questo scenario definendo una classe Temperature
che implementa le interfacce ICelsius
e IFahrenheit
come implementazioni esplicite dell'interfaccia.
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
Enumerazioni
Le enumerazioni conformi a CLS devono seguire queste regole:
Il tipo sottostante dell'enumerazione deve essere un intero intrinseco conforme a CLS (Byte, Int16, Int32o Int64). Ad esempio, il codice seguente tenta di definire un'enumerazione il cui tipo sottostante è UInt32 e genera un avviso del compilatore.
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 ' ~~~~
Un tipo di enumerazione deve avere un singolo campo di istanza denominato
Value__
contrassegnato con l'attributo FieldAttributes.RTSpecialName . In questo modo è possibile fare riferimento al valore del campo in modo implicito.Un'enumerazione include campi statici letterali i cui tipi corrispondono al tipo dell'enumerazione stessa. Ad esempio, se si definisce un'enumerazione
State
con i valoriState.On
,State.Off
,State.On
eState.Off
, siaState.On
cheState.Off
sono campi statici letterali il cui tipo è .Esistono due tipi di enumerazioni:
Enumerazione che rappresenta un set di valori interi denominati che si escludono a vicenda. Questo tipo di enumerazione è indicato dall'assenza dell'attributo System.FlagsAttribute personalizzato.
Enumerazione che rappresenta un insieme di flag di bit che possono essere combinati per generare un valore senza nome. Questo tipo di enumerazione è indicato dalla presenza dell'attributo System.FlagsAttribute personalizzato.
Per ulteriori informazioni, vedere la documentazione sulla struttura Enum.
Il valore di un'enumerazione non è limitato all'intervallo dei valori specificati. In altre parole, l'intervallo di valori in un'enumerazione è l'intervallo del valore sottostante. È possibile utilizzare il Enum.IsDefined metodo per determinare se un valore specificato è un membro di un'enumerazione.
Membri di tipo in generale
La Specifica Common Language richiede che tutti i campi e i metodi possano essere accessibili come membri di una determinata classe. Pertanto, i campi e i metodi statici globali (ovvero i campi statici o i metodi definiti a parte un tipo) non sono conformi a CLS. Se si tenta di includere un campo o un metodo globale nel codice sorgente, i compilatori C# e Visual Basic generano un errore del compilatore.
Common Language Specification supporta solo la convenzione di chiamata gestita standard. Non supporta convenzioni e metodi di chiamata non gestiti con elenchi di argomenti variabili contrassegnati con la varargs
parola chiave . Per gli elenchi di argomenti variabili compatibili con la convenzione di chiamata gestita standard, usare l'attributo o l'implementazione ParamArrayAttribute del singolo linguaggio, ad esempio la params
parola chiave in C# e la ParamArray
parola chiave in Visual Basic.
Accessibilità dei membri
L'override di un membro ereditato non può modificare l'accessibilità di tale membro. Ad esempio, un metodo pubblico in una classe base non può essere sottoposto a override da un metodo privato in una classe derivata. Esiste un'eccezione: un membro protected internal
(in C#) o Protected Friend
(in Visual Basic) in un assembly che viene sottoposto a override da un tipo in un assembly diverso. In tal caso, l'accessibilità dell'override è Protected
.
Nell'esempio seguente viene illustrato l'errore generato quando l'attributo CLSCompliantAttribute è impostato su true
e Human
, che è una classe derivata da Animal
, tenta di modificare l'accessibilità della Species
proprietà da pubblica a privata. L'esempio viene compilato correttamente se l'accessibilità viene modificata in pubblico.
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
I tipi nella firma di un membro devono essere accessibili ogni volta che tale membro è accessibile. Ciò significa, ad esempio, che un membro pubblico non può includere un parametro il cui tipo è privato, protetto o interno. Nell'esempio seguente viene illustrato l'errore del compilatore che si verifica quando un StringWrapper
costruttore di classe espone un valore di enumerazione interno StringOperationType
che determina come avvolgere un valore stringa.
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)
' ~~~~~~~~~~~~~~~~~~~
Tipi e membri generici
I tipi annidati hanno sempre almeno tanti parametri generici quanti il loro tipo contenitore. Questi corrispondono in base alla posizione ai parametri generici nel tipo di inclusione. Il tipo generico può includere anche nuovi parametri generici.
La relazione tra i parametri di tipo generico di un tipo contenitore e i relativi tipi annidati può essere nascosta dalla sintassi di singoli linguaggi. Nell'esempio seguente un tipo Outer<T>
generico contiene due classi annidate e Inner1A
Inner1B<U>
. Le chiamate al ToString
metodo , che ogni classe eredita da Object.ToString(), mostrano che ogni classe nidificata include i parametri di tipo della relativa classe contenitore.
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]
I nomi dei tipi generici vengono codificati nel nome del modulo'n, dove name è il nome del tipo, ' è un valore letterale carattere e n è il numero di parametri dichiarati nel tipo o, per i tipi generici annidati, il numero di parametri di tipo appena introdotti. Questa codifica dei nomi di tipi generici è principalmente di interesse per gli sviluppatori che utilizzano la reflection per accedere ai tipi generici conformi a CLS in una libreria.
Se i vincoli vengono applicati a un tipo generico, tutti i tipi usati come vincoli devono essere conformi anche a CLS. Nell'esempio seguente viene definita una classe denominata BaseClass
che non è conforme a CLS e una classe generica denominata BaseCollection
il cui parametro di tipo deve derivare da BaseClass
. Tuttavia, poiché BaseClass
non è conforme a CLS, il compilatore genera un avviso.
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)
' ~~~~~~~~~
Se un tipo generico è derivato da un tipo di base generico, deve ripetere qualsiasi vincolo in modo che possa garantire che vengano soddisfatti anche i vincoli sul tipo di base. Nell'esempio seguente viene definito un oggetto Number<T>
che può rappresentare qualsiasi tipo numerico. Definisce anche una FloatingPoint<T>
classe che rappresenta un valore a virgola mobile. Tuttavia, il codice sorgente non viene compilato, perché non applica il vincolo a Number<T>
(che T deve essere un tipo valore) a FloatingPoint<T>
.
using System;
[assembly:CLSCompliant(true)]
public class Number<T> where T : struct
{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;
public Number(T value)
{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}
public T Add(T value)
{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}
public T Subtract(T value)
{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}
public class FloatingPoint<T> : Number<T>
{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to compile the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly: CLSCompliant(True)>
Public Class Number(Of T As Structure)
' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double
Public Sub New(value As T)
Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub
Public Function Add(value As T) As T
Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function
Public Function Subtract(value As T) As T
Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class
Public Class FloatingPoint(Of T) : Inherits Number(Of T)
Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to compile the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~
L'esempio viene compilato correttamente se il vincolo viene aggiunto alla FloatingPoint<T>
classe .
using System;
[assembly:CLSCompliant(true)]
public class Number<T> where T : struct
{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;
public Number(T value)
{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}
public T Add(T value)
{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}
public T Subtract(T value)
{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}
public class FloatingPoint<T> : Number<T> where T : struct
{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly: CLSCompliant(True)>
Public Class Number(Of T As Structure)
' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double
Public Sub New(value As T)
Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub
Public Function Add(value As T) As T
Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function
Public Function Subtract(value As T) As T
Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class
Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)
Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
Common Language Specification impone un modello conservativo per ogni istanza per i tipi annidati e i membri protetti. I tipi generici aperti non possono esporre campi o membri con firme contenenti un'istanza specifica di un tipo generico annidato e protetto. I tipi non generici che estendono un'istanza specifica di una classe o interfaccia di base generica non possono esporre campi o membri con delle firme che contengono un'istanza diversa di un tipo generico protetto annidato.
Nell'esempio seguente viene definito un tipo generico ( C1<T>
o C1(Of T)
in Visual Basic) e una classe C1<T>.N
protetta (o C1(Of T).N
in Visual Basic).
C1<T>
ha due metodi, M1
e M2
. Tuttavia, M1
non è conforme a CLS perché tenta di restituire un C1<int>.N
oggetto (o C1(Of Integer).N
) da C1<T> (o C1(Of T)
). Una seconda classe, C2
, è derivata da C1<long>
(o C1(Of Long)
). Ha due metodi, M3
e M4
.
M3
non è conforme a CLS perché tenta di restituire un C1<int>.N
oggetto (o C1(Of Integer).N
) da una sottoclasse di C1<long>
. I compilatori del linguaggio possono essere ancora più restrittivi. In questo esempio Visual Basic visualizza un errore quando tenta di compilare 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)
' ~~~~~~~~~~~~~
Costruttori
I costruttori nelle classi e nelle strutture conformi a CLS devono seguire queste regole:
Un costruttore di una classe derivata deve chiamare il costruttore dell'istanza della relativa classe di base prima di accedere ai dati dell'istanza ereditata. Questo requisito è dovuto al fatto che i costruttori della classe base non vengono ereditati dalle classi derivate. Questa regola non si applica alle strutture, che non supportano l'ereditarietà diretta.
In genere, i compilatori applicano questa regola indipendentemente dalla conformità a CLS, come illustrato nell'esempio seguente. Crea una
Doctor
classe derivata da unaPerson
classe , ma laDoctor
classe non riesce a chiamare il costruttore della classe per inizializzare i campi dell'istanzaPerson
ereditata.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() ' ~~~
Non è possibile chiamare un costruttore di oggetti tranne per creare un oggetto . Inoltre, un oggetto non può essere inizializzato due volte. Questo significa, ad esempio, che Object.MemberwiseClone e i metodi di deserializzazione non devono chiamare costruttori.
Proprietà
Le proprietà nei tipi conformi a CLS devono seguire queste regole:
Una proprietà deve avere un setter, un getter o entrambi. In un assembly, questi vengono implementati come metodi speciali, il che significa che verranno visualizzati come metodi separati (il getter è denominato
get_
propertyname e il setter èset_
propertyname) contrassegnato comeSpecialName
nei metadati dell'assembly. I compilatori C# e Visual Basic applicano questa regola automaticamente senza la necessità di applicare l'attributo CLSCompliantAttribute .Il tipo di una proprietà è il tipo di output del getter della proprietà e l'ultimo argomento del setter. Questi tipi devono essere conformi a CLS e gli argomenti non possono essere assegnati alla proprietà per riferimento, ovvero non possono essere puntatori gestiti.
Se una proprietà ha sia un getter che un setter, devono essere entrambi virtuali, statici o entrambi. I compilatori C# e Visual Basic applicano automaticamente questa regola tramite la relativa sintassi di definizione delle proprietà.
Avvenimenti
Un evento è definito dal nome e dal relativo tipo. Il tipo di evento è un tipo di delegato utilizzato per rappresentare l'evento. Ad esempio, l'evento AppDomain.AssemblyResolve è di tipo ResolveEventHandler. Oltre all'evento stesso, tre metodi con nomi basati sul nome dell'evento forniscono l'implementazione dell'evento e vengono contrassegnati come SpecialName
nei metadati dell'assembly:
Metodo per l'aggiunta di un gestore eventi denominato
add_
EventName. Ad esempio, il metodo di sottoscrizione dell'evento per l'evento AppDomain.AssemblyResolve è denominatoadd_AssemblyResolve
.Metodo per rimuovere un gestore eventi denominato
remove_
EventName. Ad esempio, il metodo di rimozione per l'evento AppDomain.AssemblyResolve è denominatoremove_AssemblyResolve
.Metodo per indicare che si è verificato l'evento, denominato
raise_
EventName.
Annotazioni
La maggior parte delle regole di Common Language Specification relative agli eventi vengono implementate dai compilatori del linguaggio e sono trasparenti per gli sviluppatori di componenti.
I metodi per aggiungere, rimuovere e generare l'evento devono avere la stessa accessibilità. Devono anche essere statici, di istanza o virtuali. I metodi per l'aggiunta e la rimozione di un evento hanno un parametro il cui tipo è il tipo delegato dell'evento. I metodi add e remove devono essere presenti o entrambi assenti.
Nell'esempio seguente viene definita una classe conforme a CLS denominata Temperature
che genera un TemperatureChanged
evento se la variazione della temperatura tra due letture è uguale o supera un valore soglia. La Temperature
classe definisce in modo esplicito un raise_TemperatureChanged
metodo in modo che possa eseguire in modo selettivo gestori eventi.
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
Sovraccarichi
La Common Language Specification impone i seguenti requisiti sui membri sovraccarichi:
I membri possono essere sovraccaricati in base al numero di parametri e al tipo di qualsiasi parametro. Convenzione di chiamata, tipo restituito, modificatori personalizzati applicati al metodo o al relativo parametro e se i parametri vengono passati per valore o per riferimento non vengono considerati quando si differenziano tra overload. Per un esempio, vedere il codice per il requisito che i nomi devono essere univoci all'interno di un ambito nella sezione Convenzioni di denominazione .
È possibile eseguire l'overload soltanto di proprietà e metodi. Non è possibile sovraccaricare campi ed eventi.
I metodi generici possono essere sovraccaricati in base al numero dei relativi parametri generici.
Annotazioni
Gli operatori op_Explicit
e op_Implicit
sono eccezioni alla regola che il valore di ritorno non è considerato parte della firma di un metodo per la risoluzione dell'overload. Questi due operatori possono essere sovraccaricati in base ai relativi parametri e al relativo valore restituito.
Eccezioni
Gli oggetti eccezione devono derivare da System.Exception o da un altro tipo derivato da System.Exception. Nell'esempio seguente viene illustrato l'errore del compilatore che restituisce quando viene usata una classe personalizzata denominata ErrorClass
per la gestione delle eccezioni.
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
' ~~~~~~~~~~~~~~
Per correggere questo errore, la ErrorClass
classe deve ereditare da System.Exception. Inoltre, è necessario eseguire l'override della Message
proprietà . Nell'esempio seguente vengono corretti questi errori per definire una ErrorClass
classe conforme a CLS.
using System;
[assembly: CLSCompliant(true)]
public class ErrorClass : Exception
{
string msg;
public ErrorClass(string errorMessage)
{
msg = errorMessage;
}
public override string Message
{
get { return msg; }
}
}
public static class StringUtilities
{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
Imports System.Runtime.CompilerServices
<Assembly: CLSCompliant(True)>
Public Class ErrorClass : Inherits Exception
Dim msg As String
Public Sub New(errorMessage As String)
msg = errorMessage
End Sub
Public Overrides ReadOnly Property Message As String
Get
Return msg
End Get
End Property
End Class
Public Module StringUtilities
<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module
Attributi
Negli assembly .NET gli attributi personalizzati forniscono un meccanismo estendibile per l'archiviazione di attributi personalizzati e il recupero di metadati sugli oggetti di programmazione, ad esempio assembly, tipi, membri e parametri del metodo. Gli attributi personalizzati devono derivare da System.Attribute o da un tipo derivato da System.Attribute
.
Nell'esempio seguente viene violata questa regola. Definisce una NumericAttribute
classe che non deriva da System.Attribute. Un errore del compilatore viene generato solo quando viene applicato l'attributo non conforme a CLS, non quando viene definita la classe .
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
' ~~~~~~~~~~~~~
Il costruttore o le proprietà di un attributo conforme a CLS possono esporre solo i tipi seguenti:
Nell'esempio seguente viene definita una DescriptionAttribute
classe che deriva da Attribute. Il costruttore della classe ha un parametro di tipo Descriptor
, quindi la classe non è conforme a CLS. Il compilatore C# genera un avviso ma viene compilato correttamente, mentre il compilatore Visual Basic non genera un avviso o un errore.
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
Attributo CLSCompliantAttribute
L'attributo CLSCompliantAttribute viene usato per indicare se un elemento di programma è conforme alla specifica common language. Il CLSCompliantAttribute(Boolean) costruttore include un singolo parametro obbligatorio, isCompliant, che indica se l'elemento del programma è conforme a CLS.
In fase di compilazione, il compilatore rileva elementi non conformi che si presume siano conformi a CLS e genera un avviso. Il compilatore non genera avvisi per i tipi o i membri dichiarati in modo esplicito come non conformi.
Gli sviluppatori di componenti possono usare l'attributo CLSCompliantAttribute
in due modi:
Per definire le parti dell'interfaccia pubblica esposte da un componente conforme a CLS e le parti non conformi a CLS. Quando l'attributo viene usato per contrassegnare elementi di programma specifici come conformi a CLS, l'uso garantisce che tali elementi siano accessibili da tutti i linguaggi e gli strumenti destinati a .NET.
Per garantire che l'interfaccia pubblica della libreria dei componenti esponga solo gli elementi del programma conformi a CLS. Se gli elementi non sono conformi a CLS, i compilatori genereranno un avviso.
Avvertimento
In alcuni casi, i compilatori del linguaggio applicano regole conformi a CLS indipendentemente dal fatto che venga usato l'attributo CLSCompliantAttribute
. Ad esempio, la definizione di un membro statico in un'interfaccia viola una regola CLS. A questo proposito, se si definisce un static
membro (in C#) o Shared
(in Visual Basic) in un'interfaccia, i compilatori C# e Visual Basic visualizzano un messaggio di errore e non riescono a compilare l'app.
L'attributo CLSCompliantAttribute è contrassegnato con un AttributeUsageAttribute attributo con valore .AttributeTargets.All Questo valore consente di applicare l'attributo CLSCompliantAttribute a qualsiasi elemento del programma, inclusi assembly, moduli, tipi (classi, strutture, enumerazioni, interfacce e delegati), membri del tipo (costruttori, metodi, proprietà, campi ed eventi), parametri, parametri, parametri, parametri generici e valori restituiti. Tuttavia, in pratica, è consigliabile applicare l'attributo solo agli assembly, ai tipi e ai membri del tipo. In caso contrario, i compilatori ignorano l'attributo e continuano a generare avvisi del compilatore ogni volta che rilevano un parametro non conforme, un parametro generico o un valore restituito nell'interfaccia pubblica della tua libreria.
Il valore dell'attributo CLSCompliantAttribute viene ereditato dagli elementi del programma contenuti. Ad esempio, se un assembly è contrassegnato come conforme a CLS, anche i relativi tipi sono conformi a CLS. Se un tipo è contrassegnato come conforme a CLS, anche i relativi tipi e membri annidati sono conformi a CLS.
È possibile eseguire in modo esplicito l'override della conformità ereditata applicando l'attributo CLSCompliantAttribute a un elemento del programma indipendente. Ad esempio, è possibile usare l'attributo CLSCompliantAttribute con un isCompliant
valore di false
per definire un tipo non conforme in un assembly conforme ed è possibile usare l'attributo con un isCompliant
valore di true
per definire un tipo conforme in un assembly non conforme. È anche possibile definire membri non conformi in un tipo conforme. Tuttavia, un tipo non conforme non può avere membri conformi, quindi non è possibile utilizzare l'attributo con un valore di isCompliant
per eseguire l'override dell'ereditarietà da un tipo non conforme true
.
Quando si sviluppano componenti, è consigliabile usare sempre l'attributo per indicare se l'assembly CLSCompliantAttribute , i relativi tipi e i relativi membri sono conformi a CLS.
Per creare componenti conformi a CLS:
Usare per contrassegnare l'assembly CLSCompliantAttribute come conforme a CLS.
Contrassegna come non conformi i tipi esposti pubblicamente nell'assembly che non rispettano la conformità alla CLS.
Contrassegnare tutti i membri esposti pubblicamente nei tipi conformi a CLS come non conformi.
Fornire un'alternativa conforme a CLS per i membri non conformi a CLS.
Se tutti i tipi e i membri non conformi sono stati contrassegnati correttamente, il compilatore non deve generare avvisi di non conformità. Tuttavia, è necessario indicare quali membri non sono conformi a CLS ed elencare le alternative conformi a CLS nella documentazione del prodotto.
Nell'esempio seguente viene usato l'attributo CLSCompliantAttribute per definire un assembly conforme a CLS e un tipo, CharacterUtilities
, che dispone di due membri non conformi a CLS. Poiché entrambi i membri sono contrassegnati con l'attributo CLSCompliant(false)
, il compilatore non genera avvisi. La classe fornisce anche un'alternativa conforme a CLS per entrambi i metodi. In genere, aggiungeremmo semplicemente due overload al metodo ToUTF16
per fornire alternative conformi a CLS. Tuttavia, poiché non è possibile eseguire l'overload dei metodi in base al valore restituito, i nomi dei metodi conformi a CLS sono diversi dai nomi dei metodi non conformi.
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
Se si sviluppa un'app invece di una libreria (ovvero, se non si espongono tipi o membri che possono essere utilizzati da altri sviluppatori di app), la conformità CLS degli elementi del programma utilizzati dall'app è di interesse solo se il linguaggio non li supporta. In tal caso, il compilatore del linguaggio genererà un errore quando si tenta di usare un elemento non conforme a CLS.
Interoperabilità tra linguaggi
L'indipendenza del linguaggio ha alcuni significati possibili. Un significato implica il consumo senza soluzione di continuità di tipi scritti in una lingua da un'applicazione scritta in un'altra lingua. Un secondo significato, che è l'obiettivo di questo articolo, prevede la combinazione di codice scritto in più linguaggi in un singolo assembly .NET.
L'esempio seguente illustra l'interoperabilità tra linguaggi creando una libreria di classi denominata Utilities.dll che include due classi e NumericLib
StringLib
. La NumericLib
classe è scritta in C# e la StringLib
classe è scritta in Visual Basic. Ecco il codice sorgente per StringUtil.vb
, che include un singolo membro, ToTitleCase
, nella relativa StringLib
classe.
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
Ecco il codice sorgente per NumberUtil.cs, che definisce una NumericLib
classe con due membri e IsEven
NearZero
.
using System;
public static class NumericLib
{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return Convert.ToInt64(number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) % 2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}
public static bool NearZero(double number)
{
return Math.Abs(number) < .00001;
}
}
Per creare un pacchetto delle due classi in un singolo assembly, è necessario compilarle in moduli. Per compilare il file di codice sorgente di Visual Basic in un modulo, usare questo comando:
vbc /t:module StringUtil.vb
Per altre informazioni sulla sintassi della riga di comando del compilatore Visual Basic, vedere Compilazione dalla riga di comando.
Per compilare il file di codice sorgente C# in un modulo, usare questo comando:
csc /t:module NumberUtil.cs
Si usano quindi le opzioni del linker per compilare i due moduli in un assembly:
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
Nell'esempio seguente vengono quindi chiamati i metodi NumericLib.NearZero
e StringLib.ToTitleCase
. Sia il codice Visual Basic che il codice C# sono in grado di accedere ai metodi in entrambe le classi.
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
Per compilare il codice Visual Basic, usare questo comando:
vbc example.vb /r:UtilityLib.dll
Per eseguire la compilazione con C#, modificare il nome del compilatore da vbc
a csc
e modificare l'estensione del file da .vb a .cs:
csc example.cs /r:UtilityLib.dll