Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
.NET est indépendant du langage. Cela signifie que, en tant que développeur, vous pouvez développer dans l’un des nombreux langages qui ciblent des implémentations .NET, telles que C#, F# et Visual Basic. Vous pouvez accéder aux types et aux membres des bibliothèques de classes développées pour les implémentations .NET sans avoir à connaître le langage dans lequel ils ont été écrits à l’origine et sans avoir à suivre les conventions du langage d’origine. Si vous êtes développeur de composants, votre composant est accessible par n’importe quelle application .NET, quel que soit son langage.
Remarque
Cette première partie de cet article traite de la création de composants indépendants du langage, c’est-à-dire des composants qui peuvent être consommés par les applications écrites dans n’importe quel langage. Vous pouvez également créer un composant ou une application unique à partir du code source écrit dans plusieurs langues ; Consultez l’interopérabilité inter-langues dans la deuxième partie de cet article.
Pour interagir entièrement avec d’autres objets écrits dans n’importe quel langage, les objets doivent exposer aux appelants uniquement les fonctionnalités communes à tous les langages. Cet ensemble commun de fonctionnalités est défini par la spécification CLS ( Common Language Specification ), qui est un ensemble de règles qui s’appliquent aux assemblys générés. La spécification du langage commun est définie dans partition I, clauses 7 à 11 de la norme ECMA-335 : Common Language Infrastructure.
Si votre composant est conforme à la spécification common language, il est garanti qu’il soit conforme à CLS et qu’il soit accessible à partir du code dans des assemblys écrits dans n’importe quel langage de programmation qui prend en charge le CLS. Vous pouvez déterminer si votre composant est conforme à la spécification common language au moment de la compilation en appliquant l’attribut CLSCompliantAttribute à votre code source. Pour plus d’informations, consultez l’attribut CLSCompliantAttribute.
Règles de conformité CLS
Cette section décrit les règles de création d’un composant conforme CLS. Pour obtenir la liste complète des règles, consultez partition I, clause 11 de la norme ECMA-335 : Common Language Infrastructure.
Remarque
La spécification du langage commun décrit chaque règle de conformité CLS telle qu’elle s’applique aux consommateurs (développeurs qui accèdent par programme à un composant conforme CLS), aux frameworks (développeurs qui utilisent un compilateur de langage pour créer des bibliothèques compatibles CLS) et aux extendeurs (développeurs qui créent un outil tel qu’un compilateur de langage ou un analyseur de code qui crée des composants compatibles CLS). Cet article se concentre sur les règles à mesure qu’elles s’appliquent aux frameworks. Notez toutefois que certaines des règles qui s’appliquent aux extendeurs peuvent également s’appliquer aux assemblys créés à l’aide de Reflection.Emit.
Pour concevoir un composant indépendant du langage, vous devez uniquement appliquer les règles de conformité CLS à l’interface publique de votre composant. Votre implémentation privée n’a pas besoin de se conformer à la spécification.
Importante
Les règles de conformité CLS s’appliquent uniquement à l’interface publique d’un composant, et non à son implémentation privée.
Par exemple, les entiers non signés autres que Byte ne sont pas conformes à CLS. Étant donné que la Person classe de l’exemple suivant expose une Age propriété de type UInt16, le code suivant affiche un avertissement du compilateur.
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
' ~~~
Vous pouvez rendre la Person classe CLS conforme en modifiant le type de la Age propriété UInt16 vers Int16, qui est un entier signé CLS conforme à 16 bits. Vous n’avez pas besoin de modifier le type du champ privé 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’interface publique d’une bibliothèque se compose des éléments suivants :
Définitions des classes publiques.
Définitions des membres publics des classes publiques et définitions des membres accessibles aux classes dérivées (c’est-à-dire, membres protégés).
Paramètres et types de retour de méthodes publiques de classes publiques, paramètres et types de retour de méthodes accessibles aux classes dérivées.
Les règles de conformité CLS sont répertoriées dans le tableau suivant. Le texte des règles est extrait détaillé de la norme ECMA-335 : Common Language Infrastructure, qui est copyright 2012 par Ecma International. Des informations plus détaillées sur ces règles se trouvent dans les sections suivantes.
| Catégorie | Consultez | Règle | Numéro de règle |
|---|---|---|---|
| Accessibilité | Accessibilité des membres | L’accessibilité ne doit pas être modifiée lors de la substitution de méthodes héritées, sauf en cas de substitution d’une méthode héritée d’un autre assembly avec accessibilité family-or-assembly. Dans ce cas, le remplacement devra posséder l'accessibilité family. |
10 |
| Accessibilité | Accessibilité des membres | La visibilité et l’accessibilité des types et des membres doivent être de telle sorte que les types dans la signature d’un membre soient visibles et accessibles chaque fois que le membre lui-même est visible et accessible. Par exemple, une méthode publique visible en dehors de son assembly ne doit pas avoir d’argument dont le type n’est visible que dans l’assembly. La visibilité et l’accessibilité des types composés d’un type générique instancié utilisé dans la signature d’un membre doivent être visibles et accessibles chaque fois que le membre lui-même est visible et accessible. Par exemple, un type générique instancié présent dans la signature d’un membre visible en dehors de son assembly ne doit pas avoir d’argument générique dont le type est visible uniquement dans l’assembly. | 12 |
| Tableaux | Tableaux | Les tableaux auront des éléments avec un type conforme à CLS, et toutes les dimensions du tableau auront des limites inférieures égales à zéro. Seul le fait qu’un élément est un tableau et que le type d’élément du tableau doit être requis pour faire la distinction entre les surcharges. Lorsque la surcharge est basée sur deux ou plusieurs types de tableau, les types d’élément seront des types nommés. | 16 |
| Attributs | Attributs | Les attributs doivent être de type System.Attributeou un type qui hérite de celui-ci. | 41 |
| Attributs | Attributs | Le CLS autorise uniquement un sous-ensemble des encodages d’attributs personnalisés. Les seuls types qui doivent apparaître dans ces encodages sont (voir Partition IV) : System.Type, , System.StringSystem.CharSystem.BooleanSystem.ByteSystem.Int16System.Int32, , , System.Int64, , System.Singleet System.Doubletout type d’énumération basé sur un type entier de base conforme CLS. | 34 |
| Attributs | Attributs | Le CLS n’autorise pas les modificateurs obligatoires visibles publiquement (modreqvoir Partition II), mais autorise les modificateurs facultatifs (modoptvoir Partition II) qu’il ne comprend pas. |
35 |
| Constructeurs | Constructeurs | Un constructeur d’objet appelle un constructeur d’instance de sa classe de base avant tout accès aux données d’instance héritées. (Cela ne s’applique pas aux types de valeur, qui n’ont pas besoin de constructeurs.) | Vingt-et-un |
| Constructeurs | Constructeurs | Un constructeur d’objet ne doit pas être appelé, sauf dans le cadre de la création d’un objet, et un objet ne doit pas être initialisé deux fois. | 22 |
| Énumérations | énumérations | Le type sous-jacent d’une énumération doit être un type entier CLS intégré, le nom du champ doit être « value__ » et ce champ doit être marqué RTSpecialName. |
7 |
| Énumérations | énumérations | Il existe deux types distincts d’énumérations, indiqués par la présence ou l’absence de l’attribut System.FlagsAttribute personnalisé (voir Bibliothèque PARTITION IV). L’une représente les valeurs entières nommées ; l’autre représente des indicateurs de bits nommés qui peuvent être combinés pour générer une valeur non nommée. La valeur d’une enum valeur n’est pas limitée aux valeurs spécifiées. |
8 |
| Énumérations | énumérations | Les champs statiques littéraux d’une énumération doivent avoir le type de l’énumération proprement dite. | 9 |
| Événements | Événements | Les méthodes qui implémentent un événement doivent être marquées SpecialName dans les métadonnées. |
29 |
| Événements | Événements | L’accessibilité d’un événement et de ses accesseurs doit être identique. | 30 |
| Événements | Événements | Les méthodes add et remove pour un événement doivent soit être toutes les deux présentes, soit toutes les deux absentes. |
31 |
| Événements | Événements | Les méthodes add et remove pour un événement devront chacune prendre un paramètre dont le type définit le type de l'événement et qui doit être dérivé de System.Delegate. |
32 |
| Événements | Événements | Les événements respectent un modèle d’affectation de noms spécifique. L’attribut SpecialName mentionné dans la règle CLS 29 doit être ignoré dans les comparaisons de noms appropriées et respecter les règles d’identificateur. | 33 |
| Exceptions | Exceptions | Les objets jetés doivent être de type System.Exception ou un type qui hérite de celui-ci. Néanmoins, les méthodes compatibles CLS ne sont pas requises pour bloquer la propagation d’autres types d’exceptions. | 40 |
| Généralités | Règles de conformité CLS | Les règles CLS s’appliquent uniquement aux parties d’un type accessibles ou visibles en dehors de l’assembly de définition. | 1 |
| Généralités | Règles de conformité CLS | Les membres de types non conformes à CLS ne seront pas marqués comme conformes à CLS. | 2 |
| Génériques | Types et membres génériques | Les types imbriqués doivent avoir au moins autant de paramètres génériques que le type englobant. Les paramètres génériques d’un type imbriqué correspondent à la position des paramètres génériques dans son type englobant. | 42 |
| Génériques | Types et membres génériques | Le nom d’un type générique doit encoder le nombre de paramètres de type déclarés sur le type non imbriqué, ou nouvellement introduit dans le type s’il est imbriqué, conformément aux règles définies ci-dessus. | 43 |
| Génériques | Types et membres génériques | Un type générique doit redéclarer suffisamment de contraintes pour garantir que toutes les contraintes sur le type de base, ou les interfaces seraient satisfaites par les contraintes de type générique. | 44 |
| Génériques | Types et membres génériques | Les types utilisés comme contraintes sur les paramètres génériques doivent eux-mêmes être conformes CLS. | 45 |
| Génériques | Types et membres génériques | La visibilité et l’accessibilité des membres (y compris les types imbriqués) dans un type générique instancié doivent être considérés comme étant limités à l’instanciation spécifique plutôt qu’à la déclaration de type générique dans son ensemble. En supposant cela, les règles de visibilité et d’accessibilité de la règle CLS 12 s’appliquent toujours. | 46 |
| Génériques | Types et membres génériques | Pour chaque méthode générique abstraite ou virtuelle, il doit y avoir une implémentation concrète par défaut (nonabstract) | 47 |
| Interfaces | Interfaces | Les interfaces compatibles CLS ne nécessitent pas la définition de méthodes non conformes CLS pour les implémenter. | 18 |
| Interfaces | Interfaces | Les interfaces compatibles CLS ne définissent pas de méthodes statiques, ni ne définissent-elles des champs. | 19 |
| Membres | Types de membres en général | Les champs et méthodes statiques globaux ne sont pas conformes CLS. | 36 |
| Membres | -- | La valeur d’une statique littérale est spécifiée à l’aide des métadonnées d’initialisation de champ. Un littéral conforme CLS doit avoir une valeur spécifiée dans les métadonnées d’initialisation de champ qui est exactement du même type que le littéral (ou du type sous-jacent, si ce littéral est un enum). |
13 |
| Membres | Types de membres en général | La contrainte vararg ne fait pas partie du CLS, et la seule convention d’appel prise en charge par le CLS est la convention d’appel managée standard. | 15 |
| Conventions d’affectation de noms | Conventions d’affectation de noms | Les assemblages doivent suivre l’annexe 7 du rapport technique 15 du standard Unicode 3.0 régissant les caractères autorisés à commencer et à être inclus dans les identificateurs, disponibles en ligne aux formes de normalisation Unicode. Les identificateurs doivent être au format canonique défini par la forme de normalisation Unicode C. À des fins CLS, deux identificateurs sont considérés comme identiques si leurs mappages minuscules (tels que spécifiés par les mappages minuscules un à un, indépendants du paramètre régional Unicode) sont identiques. Autrement dit, pour que deux identificateurs soient considérés comme différents dans le cadre de la spécification CLS, ils doivent être différenciés par d’autres éléments que leur casse. Toutefois, pour remplacer une définition héritée, l’interface CLI nécessite l’encodage précis de la déclaration d’origine. | 4 |
| Surcharge | Conventions d’affectation de noms | Tous les noms introduits dans une portée conforme CLS doivent être distincts, indépendamment de leur type, sauf quand les noms sont identiques et résolus par surcharge. Autrement dit, alors que le CTS permet à un type unique d’utiliser le même nom pour une méthode et un champ, le CLS ne le fait pas. | 5 |
| Surcharge | Conventions d’affectation de noms | Les champs et les types imbriqués doivent être distincts par comparaison d’identificateurs seul, même si le CTS permet de distinguer les signatures distinctes. Les méthodes, les propriétés et les événements qui ont le même nom (par comparaison d’identificateurs) diffèrent par plus que le type de retour, sauf spécifié dans la règle CLS 39 | 6 |
| Surcharge | Surcharges | Seules les propriétés et méthodes peuvent être surchargées. | 37 |
| Surcharge | Surcharges | Les propriétés et méthodes peuvent être surchargées uniquement en fonction du nombre et des types de leurs paramètres, à l’exception des opérateurs de conversion nommés op_Implicit et op_Explicit, qui peuvent également être surchargés en fonction de leur type de retour. |
38 |
| Surcharge | -- | Si deux méthodes compatibles CLS ou plus déclarées dans un type ont le même nom et, pour un ensemble spécifique d’instanciations de type, elles ont le même paramètre et les mêmes types de retour, toutes ces méthodes doivent être sémantiquement équivalentes à ces instanciations de type. | 48 |
| Propriétés | Propriétés | Les méthodes qui implémentent les méthodes getter et setter d’une propriété doivent être marquées SpecialName dans les métadonnées. |
Vingt-quatre |
| Propriétés | Propriétés | Les accesseurs d’une propriété doivent tous être statiques, tous virtuels ou toutes être des instances. | 26 |
| Propriétés | Propriétés | Le type d’une propriété doit être le type de retour du getter et le type du dernier argument du setter. Les types des paramètres de la propriété doivent être ceux du getter et ceux de tous les paramètres à l'exception du dernier du setter. Tous ces types devront être conformes à CLS et ne pas être des pointeurs managés (à savoir, ils ne doivent pas être passés par référence). | 27 |
| Propriétés | Propriétés | Les propriétés doivent respecter un modèle d’affectation de noms spécifique. L’attribut mentionné dans la SpecialName règle CLS 24 doit être ignoré dans les comparaisons de noms appropriées et respecter les règles d’identificateur. Une propriété doit avoir une méthode getter, une méthode setter ou les deux. |
28 |
| Conversion de type | Conversion de type | Si op_Implicit ou op_Explicit est fourni, un autre moyen de fournir le forçage doit être fourni. | 39 |
| Les types | Types et signatures de membres de types | Les types de valeurs encadrés ne sont pas conformes à CLS. | 3 |
| Les types | Types et signatures de membres de types | Tous les types apparaissant dans une signature doivent être conformes aux CLS. Tous les types composant un type générique instancié doivent être conformes aux spécifications CLS (Common Language Specification). | 11 |
| Les types | Types et signatures de membres de types | Les références typées ne sont pas conformes CLS. | 14 |
| Les types | Types et signatures de membres de types | Les types de pointeurs non managés ne sont pas conformes CLS. | 17 |
| Les types | Types et signatures de membres de types | Les classes compatibles CLS, les types de valeur et les interfaces ne nécessitent pas l’implémentation de membres non conformes CLS | 20 |
| Les types | Types et signatures de membres de types | System.Object est conforme CLS. Toute autre classe conforme CLS hérite d’une classe conforme CLS. | 23 |
Index des sous-sections :
- Types et signatures de membres de types
- Conventions d’affectation de noms
- Conversion de type
- Tableaux
- Interfaces
- énumérations
- Types de membres en général
- Accessibilité des membres
- Types et membres génériques
- Constructeurs
- Propriétés
- Événements
- Surcharges
- Exceptions
- Attributs
Types et signatures de membres de types
Le type System.Object est conforme CLS et est le type de base de tous les types d’objets dans le système de type .NET. L’héritage dans .NET est implicite (par exemple, la classe String hérite implicitement de la Object classe) ou explicite (par exemple, la classe CultureNotFoundException hérite explicitement de la classe ArgumentException , qui hérite explicitement de la classe Exception . Pour qu’un type dérivé soit conforme CLS, son type de base doit également être conforme CLS.
L’exemple suivant montre un type dérivé dont le type de base n’est pas conforme CLS. Il définit une classe de base Counter qui utilise un entier 32 bits non signé comme compteur. Étant donné que la classe fournit des fonctionnalités de compteur en encapsulant un entier non signé, la classe est marquée comme non conforme CLS. Par conséquent, une classe dérivée NonZeroCounter n’est pas non plus conforme 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
' ~~~~~~~~~~~~~~
Tous les types qui apparaissent dans les signatures de membre, y compris le type de retour d’une méthode ou un type de propriété, doivent être conformes CLS. En outre, pour les types génériques :
Tous les types qui composent un type générique instancié doivent être conformes CLS.
Tous les types utilisés comme contraintes sur les paramètres génériques doivent être conformes CLS.
Le système de type commun .NET inclut de nombreux types intégrés pris en charge directement par le Common Language Runtime et spécialement encodés dans les métadonnées d’un assembly. De ces types intrinsèques, les types répertoriés dans le tableau suivant sont conformes à CLS.
| Type conforme à CLS | Descriptif |
|---|---|
| d’octets | Entier 8 bits non signé |
| Int16 | Entier signé 16 bits |
| Int32 | Entier signé 32 bits |
| Int64 | Entier signé 64 bits |
| Demi | Valeur à virgule flottante demi-précision |
| Unique | Valeur à virgule flottante simple précision |
| Double | Valeur à virgule flottante double précision |
| Booléen | type de valeur vrai ou faux |
| Caractère | Unité de code encodée UTF-16 |
| Décimal | Nombre décimal sans virgule flottante |
| IntPtr | Pointeur ou handle d'une taille définie par la plateforme |
| Chaîne | Collection de zéro, un ou plusieurs objets Char |
Les types intrinsèques répertoriés dans le tableau suivant ne sont pas conformes CLS.
| Type non conforme | Descriptif | Alternative à la conformité CLS |
|---|---|---|
| SByte | Type de données entier signé 8 bits | Int16 |
| UInt16 | Entier non signé 16 bits | Int32 |
| UInt32 | Entier non signé 32 bits | Int64 |
| UInt64 | Entier non signé 64 bits | Int64 (peut dépasser), BigInteger ou Double |
| UIntPtr | Pointeur ou handle non signé | IntPtr |
La bibliothèque de classes .NET ou toute autre bibliothèque de classes peut inclure d’autres types qui ne sont pas conformes à CLS, par exemple :
Types de valeurs encadrés. L’exemple C# suivant crée une classe qui a une propriété publique de type
int*nomméeValue. Étant donné qu’il s’agit d’unint*type valeur encapsulée, le compilateur le signale comme non conforme à la spécification 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-compliantRéférences typées, qui sont des constructions spéciales qui contiennent une référence à un objet et une référence à un type. Les références typées sont représentées dans .NET par la TypedReference classe.
Si un type n'est pas conforme à CLS, vous devez lui appliquer l'attribut CLSCompliantAttribute avec une valeur isCompliant de false. Pour plus d’informations, consultez l’attribut CLSCompliantAttribute.
L’exemple suivant illustre le problème de conformité au CLS dans la signature d'une méthode et l’instanciation d'un type générique. Il définit une InvoiceItem classe avec une propriété de type UInt32, une propriété de type Nullable<UInt32>et un constructeur avec des paramètres de type UInt32 et Nullable<UInt32>. Vous obtenez quatre avertissements du compilateur lorsque vous essayez de compiler cet exemple.
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
' ~~~~~~~~~
Pour éliminer les avertissements du compilateur, remplacez les types non conformes CLS dans l’interface InvoiceItem publique par des types conformes :
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
En plus des types spécifiques répertoriés, certaines catégories de types ne sont pas conformes CLS. Il s’agit notamment des types de pointeurs non managés et des types de pointeurs de fonction. L’exemple suivant génère un avertissement du compilateur, car il utilise un pointeur vers un entier pour créer un tableau d’entiers.
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
Pour les classes abstraites compatibles CLS (c’est-à-dire, les classes marquées comme abstract en C# ou en MustInherit Visual Basic), tous les membres de la classe doivent également être conformes à CLS.
Conventions d’affectation de noms
Étant donné que certains langages de programmation ne respectent pas la casse, les identificateurs (tels que les noms d'espaces de noms, de types et de membres) doivent se différencier par autre chose que la casse. Deux identificateurs sont considérés comme équivalents si leurs mappages minuscules sont identiques. L’exemple C# suivant définit deux classes publiques et Personperson. Étant donné qu’ils diffèrent uniquement par cas, le compilateur C# les signale comme non conformes 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)
Les identificateurs de langage de programmation, tels que les noms d’espaces de noms, de types et de membres, doivent être conformes à la norme Unicode. Cela signifie que :
Le premier caractère d’un identificateur peut être une lettre majuscule Unicode, une lettre minuscule, une lettre en casse titre, une lettre modificateur, une autre lettre ou une lettre nombre. Pour plus d’informations sur les catégories de caractères Unicode, consultez l’énumération System.Globalization.UnicodeCategory .
Les caractères suivants peuvent provenir de n’importe laquelle des catégories, comme le premier caractère, et peuvent également inclure des marques de non-espacement, des nombres décimaux, des ponctuations de connecteur et des codes de mise en forme.
Avant de comparer des identificateurs, vous devez filtrer les codes de mise en forme et convertir les identificateurs en formulaire de normalisation Unicode C, car un caractère unique peut être représenté par plusieurs unités de code codées en UTF-16. Les séquences de caractères qui produisent les mêmes unités de code dans le formulaire de normalisation Unicode C ne sont pas conformes clS. L’exemple suivant définit une propriété nommée Å, qui se compose du caractère ANGSTROM SIGN (U+212B) et d’une deuxième propriété nommée Å, qui se compose du caractère LETTRE MAJUSCULE LATINE A WITH RING ABOVE (U+00C5). Les compilateurs C# et Visual Basic signalent le code source comme non conforme 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
' ~
Les noms de membres dans une portée donnée (tels que les espaces de noms dans un assembly, les types dans un espace de noms ou les membres dans un type) doivent être uniques, à l'exception des noms résolus par la surcharge. Cette exigence est plus stricte que celle du système de type commun, ce qui permet à plusieurs membres d’une étendue d’avoir des noms identiques tant qu’ils sont différents types de membres (par exemple, il s’agit d’une méthode et d’un champ). En particulier, pour les membres de types :
Les champs et les types imbriqués se distinguent uniquement par leur nom.
Les méthodes, les propriétés et les événements qui ont le même nom doivent différer en plus du type de retour.
L’exemple suivant illustre l’exigence que les noms de membres doivent être uniques dans leur étendue. Il définit une classe nommée Converter qui inclut quatre membres nommés Conversion. Trois sont des méthodes, et l’une est une propriété. La méthode qui inclut un Int64 paramètre est nommée de manière unique, mais les deux méthodes avec un Int32 paramètre ne sont pas, car la valeur de retour n’est pas considérée comme une partie de la signature d’un membre. La Conversion propriété enfreint également cette exigence, car les propriétés ne peuvent pas avoir le même nom que les méthodes surchargées.
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
' ~~~~~~~~~~
Les langues individuelles incluent des mots clés uniques, de sorte que les langues qui ciblent le Common Language Runtime doivent également fournir un mécanisme permettant de référencer des identificateurs (tels que des noms de types) qui coïncident avec les mots clés. Par exemple, case il s’agit d’un mot clé en C# et en Visual Basic. Toutefois, l’exemple Visual Basic suivant est en mesure de lever l’ambiguïté d’une classe nommée case du mot clé case à l’aide d’accolades ouvrantes et fermantes. Sinon, l’exemple génère le message d’erreur « Le mot clé n’est pas valide en tant qu’identificateur » et ne peut pas être compilé.
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’exemple C# suivant est en mesure d’instancier la classe case à l’aide des @ symboles pour lever l’ambiguïté entre l’identificateur et le mot-clé. Sans cela, le compilateur C# affiche deux messages d’erreur, « Type attendu » et « Terme d’expression non valide « case ».
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
Conversion de type
La spécification du langage commun définit deux opérateurs de conversion :
op_Implicit, qui est utilisé pour élargir les conversions qui n’entraînent pas de perte de données ou de précision. Par exemple, la Decimal structure inclut un opérateur surchargéop_Implicitpour convertir des valeurs de types intégraux et des valeurs Char en valeurs Decimal.op_Explicit, qui est utilisé pour affiner les conversions qui peuvent entraîner une perte de magnitude (une valeur est convertie en une valeur qui a une plage plus petite) ou une précision. Par exemple, la structure Decimal inclut un opérateurop_Explicitsurchargé pour convertir les valeurs Double et Single en Decimal et pour convertir les valeurs Decimal en valeurs intégrales, Double, Single, et Char.
Toutefois, tous les langages ne prennent pas en charge la surcharge des opérateurs ou la définition d’opérateurs personnalisés. Si vous choisissez d’implémenter ces opérateurs de conversion, vous devez également fournir un autre moyen d’effectuer la conversion. Nous vous recommandons de fournir Fromles méthodes Xxx et ToXxx.
L’exemple suivant définit les conversions implicites et explicites conformes à CLS. Il crée une UDouble classe qui représente un nombre non signé, à double précision et à virgule flottante. Il fournit des conversions implicites de UDouble vers Double et pour des conversions explicites de UDouble vers Single, Double vers UDoubleet Single vers UDouble. Il définit également une méthode ToDouble comme alternative à l’opérateur de conversion implicite et les méthodes ToSingle, FromDouble et FromSingle comme alternatives aux opérateurs de conversion explicites.
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
Tableaux
Les tableaux conformes à CLS respectent les règles suivantes :
Toutes les dimensions d’un tableau doivent avoir une limite inférieure de zéro. L'exemple suivant crée un tableau non conforme aux normes CLS dont la borne inférieure est une. Malgré la présence de l’attribut CLSCompliantAttribute , le compilateur ne détecte pas que le tableau retourné par la
Numbers.GetTenPrimesméthode n’est pas conforme 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 ClassTous les éléments du tableau doivent être de types conformes à CLS. L’exemple suivant définit deux méthodes qui retournent des tableaux non conformes CLS. La première retourne un tableau de UInt32 valeurs. La deuxième retourne un Object tableau qui inclut les valeurs Int32 et UInt32. Bien que le compilateur identifie le premier tableau comme non conforme en raison de son UInt32 type, il ne parvient pas à reconnaître que le deuxième tableau inclut des éléments non conformes 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 résolution de surcharge pour les méthodes qui ont des paramètres de tableau est basée sur le fait qu’elles sont des tableaux et sur leur type d’élément. Pour cette raison, la définition suivante d’une méthode surchargée
GetSquaresest conforme 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
Interfaces
Les interfaces compatibles CLS peuvent définir des propriétés, des événements et des méthodes virtuelles (méthodes sans implémentation). Une interface compatible CLS ne peut pas avoir l’une des options suivantes :
Méthodes statiques ou champs statiques. Les compilateurs C# et Visual Basic génèrent des erreurs de compilateur si vous définissez un membre statique dans une interface.
Champs. Les compilateurs C# et Visual Basic génèrent des erreurs de compilateur si vous définissez un champ dans une interface.
Méthodes qui ne sont pas conformes à CLS. Par exemple, la définition d’interface suivante inclut une méthode,
INumber.GetUnsignedqui est marquée comme non conforme CLS. Cet exemple génère un avertissement du compilateur.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 ' ~~~~~~~~~~~En raison de cette règle, pour implémenter des membres non conformes à CLS, il n'est pas nécessaire d'utiliser des types conformes à CLS. Si une infrastructure conforme CLS expose une classe qui implémente une interface conforme non CLS, elle doit également fournir des implémentations concrètes de tous les membres non conformes CLS.
Les compilateurs de langage compatibles CLS doivent également permettre à une classe de fournir des implémentations distinctes de membres qui ont le même nom et la même signature dans plusieurs interfaces. C# et Visual Basic prennent en charge les implémentations d’interface explicites pour fournir différentes implémentations de méthodes nommées identiques. Visual Basic prend également en charge le Implements mot clé, qui vous permet de désigner explicitement l’interface et le membre qu’un membre particulier implémente. L’exemple suivant illustre ce scénario en définissant une classe Temperature qui implémente les interfaces ICelsius et IFahrenheit en tant qu’implémentations d’interface explicites.
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
Énumérations
Les énumérations compatibles CLS doivent respecter les règles suivantes :
Le type sous-jacent de l’énumération doit être un entier conforme CLS intrinsèque (Byte, , Int16Int32ou Int64). Par exemple, le code suivant tente de définir une énumération dont le type sous-jacent est UInt32 et génère un avertissement du compilateur.
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 type d’énumération doit avoir un champ d’instance unique nommé
Value__marqué avec l’attribut FieldAttributes.RTSpecialName . Cela vous permet de référencer implicitement la valeur du champ.Une énumération inclut des champs statiques littérals dont les types correspondent au type de l’énumération proprement dite. Par exemple, si vous définissez une énumération
Stateavec des valeurs telles queState.OnetState.Off, alorsState.OnetState.Offsont deux champs statiques dont le type estState.Il existe deux types d’énumérations :
Énumération qui représente un ensemble de valeurs entières nommées mutuellement exclusives. Ce type d’énumération est indiqué par l’absence de l’attribut System.FlagsAttribute personnalisé.
Énumération qui représente un ensemble d’indicateurs de bits pouvant être combinés pour générer une valeur sans nom. Ce type d’énumération est indiqué par la présence de l’attribut System.FlagsAttribute personnalisé.
Pour plus d’informations, consultez la documentation de la Enum structure.
La valeur d’une énumération n’est pas limitée à la plage de ses valeurs spécifiées. En d’autres termes, la plage de valeurs d’une énumération est la plage de sa valeur sous-jacente. Vous pouvez utiliser la Enum.IsDefined méthode pour déterminer si une valeur spécifiée est membre d’une énumération.
Types de membres en général
La spécification du langage commun nécessite que tous les champs et méthodes soient accessibles en tant que membres d’une classe particulière. Par conséquent, les champs et méthodes statiques globaux (autrement dit, les champs statiques ou les méthodes définis à l’exception d’un type) ne sont pas conformes CLS. Si vous essayez d’inclure un champ global ou une méthode dans votre code source, les compilateurs C# et Visual Basic génèrent une erreur de compilateur.
La spécification du langage commun prend uniquement en charge la convention d’appel managée standard. Elle ne prend pas en charge les conventions et méthodes d’appel non managées avec des listes d’arguments variables marquées avec le varargs mot clé. Pour les listes d’arguments variables compatibles avec la convention d’appel managé standard, utilisez l’attribut ParamArrayAttribute ou l’implémentation de la langue individuelle, comme le params mot clé en C# et le ParamArray mot clé en Visual Basic.
Accessibilité des membres
Le remplacement d'un membre hérité ne peut pas modifier l'accessibilité de ce membre. Par exemple, une méthode publique dans une classe de base ne peut pas être substituée par une méthode privée dans une classe dérivée. Il existe une exception : un membre protected internal (en C#) ou Protected Friend (en Visual Basic) dans un assemblage remplacé par un type dans un autre assemblage. Dans ce cas, l'accessibilité du remplacement est Protected.
L’exemple suivant illustre l’erreur générée lorsque l’attribut CLSCompliantAttribute est défini à true, et Human, qui est une classe dérivée de Animal, tente de modifier l’accessibilité de la propriété Species de publique à privée. L’exemple compile correctement si son accessibilité est modifiée en public.
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
Les types de la signature d’un membre doivent être accessibles chaque fois que ce membre est accessible. Par exemple, cela signifie qu’un membre public ne peut pas inclure un paramètre dont le type est privé, protégé ou interne. L’exemple suivant illustre l’erreur du compilateur qui se produit lorsqu’un StringWrapper constructeur de classe expose une valeur d’énumération interne StringOperationType qui détermine comment une valeur de chaîne doit être encapsulée.
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)
' ~~~~~~~~~~~~~~~~~~~
Types et membres génériques
Les types imbriqués ont toujours au moins autant de paramètres génériques que leur type englobant. Celles-ci correspondent à la position des paramètres génériques dans le type englobant. Le type générique peut également inclure de nouveaux paramètres génériques.
La relation entre les paramètres de type générique d’un type contenant et ses types imbriqués peut être masquée par la syntaxe des langages individuels. Dans l’exemple suivant, un type Outer<T> générique contient deux classes imbriquées, Inner1A et Inner1B<U>. Les appels à la méthode ToString, que chaque classe hérite de Object.ToString(), montrent que chaque classe imbriquée inclut les paramètres de type de la classe qui les contient.
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]
Les noms de types génériques sont encodés sous la forme nom`n, où nom est le nom du type, ` est un littéral de caractère, et n est le nombre de paramètres déclarés pour le type ou, pour les types génériques imbriqués, le nombre de nouveaux paramètres de type introduits. Cet encodage de noms de types génériques est principalement intéressant pour les développeurs qui utilisent la réflexion pour accéder à des types génériques conformes au CLS dans une bibliothèque.
Si des contraintes sont appliquées à un type générique, tous les types utilisés comme contraintes doivent également être conformes CLS. L’exemple suivant définit une classe nommée BaseClass qui n’est pas conforme CLS et une classe générique nommée BaseCollection dont le paramètre de type doit dériver BaseClass. Toutefois, étant donné qu’il BaseClass n’est pas conforme à CLS, le compilateur émet un avertissement.
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)
' ~~~~~~~~~
Si un type générique est dérivé d’un type de base générique, il doit redéclarer toutes les contraintes afin qu’elle puisse garantir que les contraintes sur le type de base sont également satisfaites. L’exemple suivant définit un Number<T> type qui peut représenter n’importe quel type numérique. Il définit également une FloatingPoint<T> classe qui représente une valeur à virgule flottante. Toutefois, le code source ne parvient pas à compiler, car il n’applique pas la contrainte sur Number<T> (auquel T doit être un type valeur) à 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’exemple compile correctement si la contrainte est ajoutée à la 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
La spécification du langage commun impose un modèle par instanciation conservateur pour les types imbriqués et les membres protégés. Les types génériques ouverts ne peuvent pas exposer de champs ou de membres avec des signatures qui contiennent une instanciation spécifique d’un type générique imbriqué et protégé. Les types non génériques qui étendent une instanciation spécifique d’une classe de base ou d’une interface générique ne peuvent pas exposer de champs ou de membres avec des signatures qui contiennent une instanciation différente d’un type générique imbriqué et protégé.
L’exemple suivant définit un type C1<T> générique (ou C1(Of T) en Visual Basic) et une classe C1<T>.N protégée (ou C1(Of T).N en Visual Basic).
C1<T> a deux méthodes, M1 et M2. Toutefois, M1 n’est pas conforme CLS, car il tente de renvoyer un C1<int>.N objet (ou C1(Of Integer).N) à partir de C1<T> (ou C1(Of T)). Une deuxième classe, est C2dérivée de C1<long> (ou C1(Of Long)). Il a deux méthodes, M3 et M4.
M3 n’est pas conforme CLS, car il tente de renvoyer un C1<int>.N objet (ou C1(Of Integer).N) à partir d’une sous-classe de C1<long>. Les compilateurs de langage peuvent être encore plus restrictifs. Dans cet exemple, Visual Basic affiche une erreur lorsqu’il tente de compiler 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)
' ~~~~~~~~~~~~~
Constructeurs
Les constructeurs dans les classes et structures compatibles CLS doivent respecter les règles suivantes :
Un constructeur d’une classe dérivée doit appeler le constructeur d’instance de sa classe de base avant d’accéder aux données d’instance héritées. Cette exigence est due au fait que les constructeurs des classes de base ne sont pas hérités par leurs classes dérivées. Cette règle ne s’applique pas aux structures qui ne prennent pas en charge l’héritage direct.
En règle générale, les compilateurs appliquent cette règle indépendamment de la conformité CLS, comme l’illustre l’exemple suivant. Il crée une
Doctorclasse dérivée d’unePersonclasse, mais laDoctorclasse ne parvient pas à appeler lePersonconstructeur de classe pour initialiser les champs d’instance hérités.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() ' ~~~Un constructeur d’objet ne peut pas être appelé à l’exception de la création d’un objet. En outre, un objet ne peut pas être initialisé deux fois. Par exemple, cela signifie que Object.MemberwiseClone et les méthodes de désérialisation ne doivent pas appeler de constructeurs.
Propriétés
Les propriétés des types conformes CLS doivent respecter les règles suivantes :
Une propriété doit posséder une méthode setter, getter ou les deux. Dans un assembly, ces méthodes sont implémentées en tant que méthodes spéciales, ce qui signifie qu’elles apparaissent sous forme de méthodes distinctes (le getter est nommé
get_propertyname et le setter estset_propertyname) marqué commeSpecialNamedans les métadonnées de l’assembly. Les compilateurs C# et Visual Basic appliquent cette règle automatiquement sans avoir à appliquer l’attribut CLSCompliantAttribute .Le type d’une propriété correspond au type de retour de la méthode getter de la propriété et du dernier argument de la méthode setter. Ces types doivent être conformes CLS et les arguments ne peuvent pas être attribués à la propriété par référence (autrement dit, ils ne peuvent pas être des pointeurs managés).
Si une propriété possède une méthode getter et une méthode setter, elles doivent être toutes les deux virtuelles, statiques ou être toutes les deux des instances. Les compilateurs C# et Visual Basic appliquent automatiquement cette règle via leur syntaxe de définition de propriété.
Événements
Un événement est défini par son nom et son type. Le type d'événement est un délégué utilisé pour indiquer l'événement. Par exemple, l’événement AppDomain.AssemblyResolve est de type ResolveEventHandler. En plus de l’événement lui-même, trois méthodes avec des noms basés sur le nom de l’événement fournissent l’implémentation de l’événement et sont marquées comme SpecialName dans les métadonnées de l’assembly :
Méthode permettant d’ajouter un gestionnaire d’événements, nommé
add_EventName. Par exemple, la méthode d’abonnement aux événements pour l’événement AppDomain.AssemblyResolve est nomméeadd_AssemblyResolve.Méthode permettant de supprimer un gestionnaire d’événements, nommé
remove_EventName. Par exemple, la méthode de suppression de l’événement AppDomain.AssemblyResolve est nomméeremove_AssemblyResolve.Méthode pour indiquer que l’événement s’est produit, nommé
raise_EventName.
Remarque
La plupart des règles de common language specification concernant les événements sont implémentées par les compilateurs de langage et sont transparentes pour les développeurs de composants.
Les méthodes d’ajout, de suppression et de déclenchement de l’événement doivent avoir la même accessibilité. Elles doivent également être statiques, instance ou virtuelles. Les méthodes d’ajout et de suppression d’un événement ont un paramètre dont le type est le type délégué d’événement. Les méthodes d’ajout et de suppression doivent être présentes ou absentes.
L’exemple suivant définit une classe conforme CLS nommée Temperature qui déclenche un TemperatureChanged événement si le changement de température entre deux lectures est égal ou dépasse une valeur de seuil. La Temperature classe définit explicitement une raise_TemperatureChanged méthode afin qu’elle puisse exécuter sélectivement des gestionnaires d’événements.
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
Surcharges
La spécification du langage commun impose les exigences suivantes aux membres surchargés :
Les membres peuvent être surchargés en fonction du nombre de paramètres et du type de n’importe quel paramètre. Convention appelante, type de retour, modificateurs personnalisés appliqués à la méthode ou à son paramètre, et si les paramètres sont passés par valeur ou par référence ne sont pas pris en compte lors de la différenciation entre les surcharges. Pour obtenir un exemple, consultez le code de l’exigence que les noms doivent être uniques dans une étendue dans la section Conventions de nommage.
Seules les propriétés et méthodes peuvent être surchargées. Les champs et les événements ne peuvent pas être surchargés.
Les méthodes génériques peuvent être surchargées en fonction du nombre de leurs paramètres génériques.
Remarque
Les opérateurs op_Explicit et op_Implicit sont des exceptions à la règle indiquant que la valeur de retour n'est pas considérée comme faisant partie d'une signature de méthode pour la résolution de la surcharge. Ces deux opérateurs peuvent être surchargés en fonction de leurs paramètres et de leur valeur de retour.
Exceptions
Les objets d’exception doivent dériver d’System.Exception ou d’un autre type dérivé de System.Exception. L’exemple suivant illustre l’erreur du compilateur qui se produit lorsqu’une classe personnalisée nommée ErrorClass est utilisée pour la gestion des exceptions.
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
' ~~~~~~~~~~~~~~
Pour corriger cette erreur, la ErrorClass classe doit hériter de System.Exception. En outre, la propriété Message doit être remplacée. L’exemple suivant corrige ces erreurs pour définir une ErrorClass classe conforme 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
Attributs
Dans les assemblys .NET, les attributs personnalisés fournissent un mécanisme extensible pour stocker des attributs personnalisés et récupérer des métadonnées sur les objets de programmation, tels que les assemblys, les types, les membres et les paramètres de méthode. Les attributs personnalisés doivent dériver de System.Attribute ou d’un type dérivé de System.Attribute.
L’exemple suivant enfreint cette règle. Il définit une NumericAttribute classe qui ne dérive pas de System.Attribute. Une erreur du compilateur se produit uniquement lorsque l’attribut non conforme CLS est appliqué, et non lorsque la classe est définie.
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
' ~~~~~~~~~~~~~
Le constructeur ou les propriétés d’un attribut conforme CLS peuvent exposer uniquement les types suivants :
L’exemple suivant définit une DescriptionAttribute classe qui dérive de Attribute. Le constructeur de classe a un paramètre de type Descriptor, de sorte que la classe n’est pas conforme CLS. Le compilateur C# émet un avertissement, mais se compile correctement, tandis que le compilateur Visual Basic n’émet pas d’avertissement ou d’erreur.
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
Attribut CLSCompliantAttribute
L’attribut CLSCompliantAttribute est utilisé pour indiquer si un élément de programme est conforme à la spécification common language. Le CLSCompliantAttribute(Boolean) constructeur inclut un paramètre obligatoire unique, isCompliant, qui indique si l’élément de programme est conforme à CLS.
Au moment de la compilation, le compilateur détecte les éléments non conformes présumés conformes à CLS et émet un avertissement. Le compilateur n’émet pas d’avertissements pour les types ou les membres qui sont explicitement déclarés comme non conformes.
Les développeurs de composants peuvent utiliser l’attribut CLSCompliantAttribute de deux façons :
Pour définir les parties de l’interface publique exposées par un composant qui sont conformes au CLS et celles qui ne le sont pas. Lorsque l’attribut est utilisé pour marquer des éléments de programme particuliers comme conformes CLS, son utilisation garantit que ces éléments sont accessibles à partir de tous les langages et outils qui ciblent .NET.
Pour vous assurer que l’interface publique de la bibliothèque de composants expose uniquement les éléments de programme conformes à CLS. Si les éléments ne sont pas conformes à CLS, les compilateurs émettent généralement un avertissement.
Avertissement
Dans certains cas, les compilateurs de langage appliquent des règles compatibles CLS, que l’attribut CLSCompliantAttribute soit utilisé ou non. Par exemple, la définition d’un membre statique dans une interface enfreint une règle CLS. À cet égard, si vous définissez un static membre (en C#) ou Shared (en Visual Basic) dans une interface, les compilateurs C# et Visual Basic affichent un message d’erreur et ne peuvent pas compiler l’application.
L'attribut CLSCompliantAttribute est marqué par un attribut AttributeUsageAttribute dont la valeur est AttributeTargets.All. Cette valeur vous permet d’appliquer l’attribut CLSCompliantAttribute à n’importe quel élément de programme, notamment des assemblys, des modules, des types (classes, structures, énumérations, interfaces et délégués), des membres de type (constructeurs, méthodes, propriétés, champs et événements), des paramètres, des paramètres génériques et des valeurs de retour. Toutefois, dans la pratique, vous devez appliquer l’attribut uniquement aux assemblages, aux types et aux membres de types. Sinon, les compilateurs ignorent l’attribut et continuent à générer des avertissements du compilateur chaque fois qu’ils rencontrent un paramètre non conforme, un paramètre générique ou une valeur de retour dans l’interface publique de votre bibliothèque.
La valeur de l’attribut CLSCompliantAttribute est héritée par les éléments de programme contenus. Par exemple, si un assemblage est marqué comme conforme à CLS, ses types sont également conformes à CLS. Si un type est marqué comme conforme CLS, ses types imbriqués et ses membres sont également conformes à CLS.
Vous pouvez remplacer explicitement la conformité héritée en appliquant l’attribut CLSCompliantAttribute à un élément de programme contenu. Par exemple, vous pouvez utiliser l’attribut CLSCompliantAttribute avec une isCompliant valeur de false définition d’un type non conforme dans un assembly conforme, et vous pouvez utiliser l’attribut avec une isCompliant valeur de true définition d’un type conforme dans un assembly non conforme. Dans un type conforme, vous pouvez également définir des membres non-conformes. Toutefois, un type non conforme ne peut pas avoir de membres conformes. Vous ne pouvez donc pas utiliser l’attribut avec une valeur isCompliant de true pour remplacer l’héritage d’un type non conforme.
Lorsque vous développez des composants, vous devez toujours utiliser l’attribut CLSCompliantAttribute pour indiquer si votre assembly, ses types et ses membres sont conformes à CLS.
Pour créer des composants conformes CLS :
Utilisez la balise CLSCompliantAttribute pour marquer votre assembly comme conforme CLS.
Marquez comme non conformes tous les types exposés publiquement dans l'assemblage qui ne sont pas conformes au CLS.
Marquez tous les membres exposés publiquement dans des types conformes à CLS comme non conformes.
Fournissez une alternative conforme à CLS pour les membres non conformes.
Si vous avez correctement marqué tous vos types et membres non conformes, votre compilateur ne doit pas émettre d’avertissements de non-conformité. Toutefois, vous devez indiquer quels membres ne sont pas conformes à CLS et répertorier leurs alternatives compatibles CLS dans la documentation de votre produit.
L’exemple suivant utilise l’attribut CLSCompliantAttribute pour définir un assembly conforme CLS et un type, CharacterUtilitiesqui a deux membres non conformes CLS. Étant donné que les deux membres sont marqués avec l’attribut CLSCompliant(false) , le compilateur ne produit aucun avertissement. La classe fournit également une alternative conforme CLS pour les deux méthodes. En règle générale, nous ajoutons généralement deux surcharges à la méthode ToUTF16 pour fournir des alternatives compatibles CLS. Toutefois, étant donné que les méthodes ne peuvent pas être surchargées en fonction de la valeur de retour, les noms des méthodes conformes CLS sont différents des noms des méthodes non conformes.
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
Si vous développez une application plutôt qu’une bibliothèque (autrement dit, si vous n’exposez pas de types ou de membres qui peuvent être consommés par d’autres développeurs d’applications), la conformité CLS des éléments de programme que votre application consomme n’est intéressante que si votre langue ne les prend pas en charge. Dans ce cas, votre compilateur de langage génère une erreur lorsque vous essayez d’utiliser un élément non conforme CLS.
Interopérabilité inter-langages
L’indépendance linguistique a quelques significations possibles. Une des significations implique la consommation de façon transparente des types écrits dans un langage à partir d’une application écrite dans un autre langage. Une deuxième signification, qui est l’objectif de cet article, consiste à combiner du code écrit dans plusieurs langages dans un seul assembly .NET.
L’exemple suivant illustre l’interopérabilité inter-langages en créant une bibliothèque de classes nommée Utilities.dll qui inclut deux classes et NumericLibStringLib. La NumericLib classe est écrite en C#, et la StringLib classe est écrite en Visual Basic. Voici le code source pour StringUtil.vb, qui inclut un seul membre, ToTitleCasedans sa 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
Voici le code source de NumberUtil.cs, qui définit une NumericLib classe qui a deux membres et IsEvenNearZero.
using System;
public static class NumericLib
{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return Convert.ToInt64(number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) % 2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}
public static bool NearZero(double number)
{
return Math.Abs(number) < .00001;
}
}
Pour empaqueter les deux classes d’un seul assembly, vous devez les compiler en modules. Pour compiler le fichier de code source Visual Basic dans un module, utilisez cette commande :
vbc /t:module StringUtil.vb
Pour plus d’informations sur la syntaxe de ligne de commande du compilateur Visual Basic, consultez Génération à partir de la ligne de commande.
Pour compiler le fichier de code source C# dans un module, utilisez cette commande :
csc /t:module NumberUtil.cs
Vous utilisez ensuite les options de l’éditeur de liens pour compiler les deux modules en un assembly :
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
L’exemple suivant appelle ensuite les méthodes NumericLib.NearZero et StringLib.ToTitleCase. Le code Visual Basic et le code C# sont en mesure d’accéder aux méthodes des deux classes.
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
Pour compiler le code Visual Basic, utilisez cette commande :
vbc example.vb /r:UtilityLib.dll
Pour compiler avec C#, changez le nom du compilateur de vbc à csc, et changez l'extension du fichier de .vb à .cs :
csc example.cs /r:UtilityLib.dll