Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
.NET es independiente del lenguaje. Esto significa que, como desarrollador, puede desarrollar en uno de los muchos lenguajes que tienen como destino implementaciones de .NET, como C#, F#, y Visual Basic. Puede acceder a los tipos y miembros de las bibliotecas de clases desarrolladas para implementaciones de .NET sin tener que conocer el lenguaje en el que se escribieron originalmente y sin tener que seguir ninguna de las convenciones del lenguaje original. Si es desarrollador de componentes, cualquier aplicación de .NET puede acceder a su componente, independientemente de su idioma.
Nota:
En esta primera parte de este artículo se describe la creación de componentes independientes del lenguaje, es decir, componentes que pueden consumir las aplicaciones escritas en cualquier lenguaje. También puede crear un único componente o aplicación a partir del código fuente escrito en varios idiomas; consulte Interoperabilidad entre lenguajes en la segunda parte de este artículo.
Para interactuar completamente con otros objetos escritos en cualquier lenguaje, los objetos deben exponer a los autores de llamadas solo las características que son comunes a todos los lenguajes. Este conjunto común de características se define mediante Common Language Specification (CLS), que es un conjunto de reglas que se aplican a los ensamblados generados. La Especificación de Lenguaje Común se define en la partición I, cláusulas 7 a 11 del Estándar ECMA-335: Infraestructura de Lenguaje Común.
Si el componente se ajusta a la Common Language Specification, se garantiza su compatibilidad con el CLS y se puede acceder desde el código de los ensamblados escritos en cualquier lenguaje de programación que admite el CLS. Puede determinar si el componente se ajusta a Common Language Specification en tiempo de compilación aplicando el atributo CLSCompliantAttribute al código fuente. Para obtener más información, vea El atributo CLSCompliantAttribute.
Reglas de cumplimiento de CLS
En esta sección se describen las reglas para crear un componente compatible con CLS. Para obtener una lista completa de las reglas, vea Partición I, cláusula 11 del estándar ECMA-335: Common Language Infrastructure.
Nota:
Common Language Specification describe cada regla para el cumplimiento de CLS, ya que se aplica a los consumidores (desarrolladores que acceden mediante programación a un componente compatible con CLS), marcos (desarrolladores que usan un compilador de lenguaje para crear bibliotecas compatibles con CLS) y extensores (desarrolladores que crean una herramienta como un compilador de lenguaje o un analizador de código que crea componentes compatibles con CLS). Este artículo se centra en las reglas a medida que se aplican a los marcos de trabajo. Sin embargo, tenga en cuenta que algunas de las reglas que se aplican a los extensores también se pueden aplicar a los ensamblados que se crean mediante Reflection.Emit.
Para diseñar un componente independiente del lenguaje, solo tiene que aplicar las reglas de cumplimiento de CLS a la interfaz pública del componente. La implementación privada no tiene que cumplir la especificación.
Importante
Las reglas de cumplimiento de CLS solo se aplican a la interfaz pública de un componente, no a su implementación privada.
Por ejemplo, los enteros sin signo diferentes a Byte no son compatibles con CLS. Dado que la Person clase del ejemplo siguiente expone una Age propiedad de tipo UInt16, el código siguiente muestra una advertencia del compilador.
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
' ~~~
Puede hacer que la Person clase CLS sea compatible cambiando el tipo de la Age propiedad de UInt16 a Int16, que es un entero de 16 bits con signo compatible con CLS. No es necesario cambiar el tipo del campo privado 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
La interfaz pública de una biblioteca consta de lo siguiente:
Definiciones de clases públicas.
Definiciones de los miembros públicos de las clases públicas y de los miembros accesibles por las clases derivadas (es decir, miembros protegidos).
Parámetros y tipos devueltos de los métodos públicos de las clases públicas y parámetros y tipos devueltos de los métodos accesibles por las clases derivadas.
Las reglas para el cumplimiento de CLS se enumeran en la tabla siguiente. El texto de las reglas se toma textualmente del estándar ECMA-335: Common Language Infrastructure, que está protegido por derechos de autor de 2012 por Ecma International. En las secciones siguientes encontrará información más detallada sobre estas reglas.
| Categoría | Vea | Regla | Número de regla |
|---|---|---|---|
| Accesibilidad | Accesibilidad de miembros | No se cambiará la accesibilidad al invalidar métodos heredados, excepto cuando se invalida un método heredado de un ensamblado diferente con accesibilidad family-or-assembly. En este caso, el reemplazo debe tener accesibilidad family. |
10 |
| Accesibilidad | Accesibilidad de miembros | La visibilidad y accesibilidad de los tipos y miembros será tal que los tipos de la firma de cualquier miembro sean visibles y accesibles siempre que el propio miembro sea visible y accesible. Por ejemplo, un método público que esté visible fuera de su ensamblado no tendrá un argumento cuyo tipo solo esté visible dentro del ensamblado. La visibilidad y accesibilidad de los tipos que componen un tipo genérico instanciado usado en la firma de cualquier miembro será visible y accesible siempre que el propio miembro sea visible y accesible. Por ejemplo, un tipo genérico instanciado presente en la firma de un miembro que es visible fuera de su ensamblaje no debe tener un argumento genérico cuyo tipo solo sea visible dentro del ensamblaje. | 12 |
| Matrices | matrices de |
Las matrices tendrán elementos con un tipo conforme a CLS y todas las dimensiones de la matriz tendrán límites inferiores de cero. Solo el hecho de que un ítem sea un array y el tipo de elementos del array basta para distinguir entre sobrecargas. Cuando la sobrecarga se basa en dos o más tipos de matriz, los tipos de elemento se denominarán tipos. | 16 |
| Atributos | Atributos | Los atributos deben ser de tipo System.Attributeo de un tipo que herede de él. | 41 |
| Atributos | Atributos | CLS solo permite un subconjunto de las codificaciones de atributos personalizados. Los únicos tipos que aparecerán en estas codificaciones son (vea Partition IV): System.Type, System.String, System.CharSystem.BooleanSystem.Byte, System.Int16System.Int32, , System.Int64, , System.Single, y System.Doublecualquier tipo de enumeración basado en un tipo entero base compatible con CLS. | 34 |
| Atributos | Atributos | CLS no permite modificadores obligatorios visibles públicamente (modreq, vea Sección II), pero permite modificadores opcionales (modopt, vea Sección II) que no entiende. |
35 |
| Constructores | Constructores | Un constructor de objetos llamará a algún constructor de instancia de su clase base antes de que se produzca cualquier acceso a los datos de instancia heredados. (Esto no se aplica a los tipos de valor, que no necesitan tener constructores). | Veintiuno |
| Constructores | Constructores | No se llamará a un constructor de objeto excepto como parte de la creación de un objeto y no se inicializará dos veces un objeto. | 22 |
| Enumeraciones | enumeraciones | El tipo subyacente de una enumeración debe ser un tipo entero CLS integrado, el nombre del campo será "value__" y ese campo se marcará como RTSpecialName. |
7 |
| Enumeraciones | enumeraciones | Hay dos tipos distintos de enumeraciones, indicadas por la presencia o ausencia del System.FlagsAttribute atributo personalizado (vea Partition IV Library). Uno representa valores enteros con nombre; el otro representa marcas de bits con nombre que se pueden combinar para generar un valor sin nombre. El valor de un enum no se limita a los valores especificados. |
8 |
| Enumeraciones | enumeraciones | Los campos estáticos literales de una enumeración tendrán el tipo de enumeración en sí. | 9 |
| Eventos | Eventos | Los métodos que implementan un evento se marcarán SpecialName en los metadatos. |
29 |
| Eventos | Eventos | La accesibilidad de un evento y de sus accesores será idéntica. | 30 |
| Eventos | Eventos | Los métodos add y remove para un evento deben estar ambos presentes o ausentes. |
31 |
| Eventos | Eventos | Los add métodos y remove para un evento tomarán un parámetro cuyo tipo define el tipo del evento y que se derivará de System.Delegate. |
32 |
| Eventos | Eventos | Los eventos se adhieren a un patrón de nomenclatura específico. El atributo SpecialName al que se hace referencia en la regla 29 de CLS se omitirá en las comparaciones de nombres adecuadas y cumplirá las reglas de identificador. | 33 |
| Excepciones | Excepciones | Los objetos que se lancen deben ser de tipo System.Exception o de un tipo que herede de este. No obstante, los métodos conformes a CLS no son necesarios para bloquear la propagación de otros tipos de excepciones. | 40 |
| General | Reglas de cumplimiento de CLS | Las reglas CLS solo se aplican a las partes de un tipo que son accesibles o visibles fuera del ensamblado de definición. | 1 |
| General | Reglas de cumplimiento de CLS | Los miembros de tipos no conformes con CLS no deben marcarse como conformes con CLS. | 2 |
| Genéricos | Tipos y miembros genéricos | Los tipos anidados deberán tener al menos tantos parámetros genéricos como el tipo contenedor. Los parámetros genéricos de un tipo anidado se corresponden por posición con los parámetros genéricos del tipo contenedor. | 42 |
| Genéricos | Tipos y miembros genéricos | El nombre de un tipo genérico codificará el número de parámetros de tipo declarados en el tipo no anidado o recién introducidos al tipo si están anidados, según las reglas definidas anteriormente. | 43 |
| Genéricos | Tipos y miembros genéricos | Un tipo genérico deberá volver a declarar las restricciones suficientes para garantizar que las restricciones del tipo base o las interfaces se cumplan con las restricciones de tipo genérico. | 44 |
| Genéricos | Tipos y miembros genéricos | Los tipos usados como restricciones en parámetros genéricos deben ser compatibles con CLS. | 45 |
| Genéricos | Tipos y miembros genéricos | La visibilidad y accesibilidad de los miembros (incluidos los tipos anidados) en un tipo genérico instanciado se considerará limitada a la instanciación específica, en lugar de estar referida a la declaración del tipo genérico en su conjunto. Suponiendo esto, todavía se aplican las reglas de visibilidad y accesibilidad de la regla 12 de CLS. | 46 |
| Genéricos | Tipos y miembros genéricos | Para cada método genérico abstracto o virtual, habrá una implementación predeterminada concreta (no abstracto). | 47 |
| Interfaces | Interfaces | Las interfaces conformes a CLS no requerirán la definición de métodos no conformes a CLS para implementarlas. | 18 |
| Interfaces | Interfaces | Las interfaces conformes a CLS no definirán métodos estáticos ni definirán campos. | 19 |
| Miembros | Miembros de tipos en general | Los campos y métodos estáticos globales no son compatibles con CLS. | 36 |
| Miembros | -- | El valor de un estático literal se especifica mediante el uso de metadatos de inicialización de campos. Un literal conforme a CLS debe tener un valor especificado en los metadatos de inicialización de campo que sea exactamente del mismo tipo que el literal (o del tipo subyacente, si ese literal es un enum). |
13 |
| Miembros | Miembros de tipos en general | La restricción vararg no forma parte de CLS y la única convención de llamada admitida por CLS es la convención de llamada administrada estándar. | 15 |
| Convenciones de nomenclatura | Convenciones de nomenclatura | Los ensamblados seguirán las directrices del anexo 7 del informe técnico 15 del estándar Unicode 3.0, que rige el conjunto de caracteres permitidos que pueden usarse como iniciales e incluirse en los identificadores. Estas directrices están disponibles en línea en Formularios de normalización Unicode. Los identificadores tendrán el formato canónico definido por la Forma de Normalización Unicode C. Para fines de CLS, dos identificadores son iguales si sus asignaciones en minúsculas (según lo especificado por las asignaciones minúsculas uno a uno, independientemente de la configuración regional de Unicode) son iguales. Es decir, para que dos identificadores se consideren diferentes según CLS, tendrán que diferenciarse en algo más que en el uso de mayúsculas y minúsculas. Sin embargo, para invalidar una definición heredada, la CLI requiere la codificación precisa de la declaración original. | 4 |
| Sobrecarga | Convenciones de nomenclatura | Todos los nombres especificados en un ámbito conforme a CLS deben ser distintos independientemente del tipo, salvo en los casos en los que los nombres sean idénticos y se resuelvan mediante sobrecarga. Es decir, mientras que el CTS permite que un solo tipo use el mismo nombre para un método y un campo, CLS no. | 5 |
| Sobrecarga | Convenciones de nomenclatura | Los campos y los tipos anidados deben ser distintos solo por comparación de identificadores, aunque el CTS permita distinguir firmas distintas. Los métodos, propiedades y eventos que tienen el mismo nombre (por comparación de identificador) variarán por más que solo el tipo de valor devuelto, excepto según lo especificado en la regla 39 de CLS. | 6 |
| Sobrecarga | Sobrecargas | Solo las propiedades y los métodos se pueden sobrecargar. | 37 |
| Sobrecarga | Sobrecargas | Las propiedades y los métodos solo se pueden sobrecargar en función del número y los tipos de sus parámetros, excepto los operadores de conversión denominados op_Implicit y op_Explicit, que también se pueden sobrecargar en función de su tipo de valor devuelto. |
38 |
| Sobrecarga | -- | Si dos o más métodos conformes a CLS declarados en un tipo tienen el mismo nombre y, para un conjunto específico de instancias de tipo, tienen el mismo parámetro y tipos devueltos, todos estos métodos serán semánticamente equivalentes en esas instancias de tipo. | 48 |
| Propiedades | Propiedades | Los métodos que implementan los métodos captadores y establecedores de una propiedad se marcarán SpecialName en los metadatos. |
veinticuatro |
| Propiedades | Propiedades | Todos los descriptores de acceso de una propiedad deben ser estáticos, virtuales o de instancia. | 26 |
| Propiedades | Propiedades | El tipo de una propiedad es el tipo de valor devuelto del método captador y el tipo del último argumento del método establecedor. Los tipos de los parámetros de la propiedad deben ser los tipos de los parámetros del método captador y los tipos de todos los parámetros del método establecedor, excepto el último. Todos estos tipos deben ser conformes a CLS y no pueden ser punteros administrados (es decir, no deben pasarse por referencia). | 27 |
| Propiedades | Propiedades | Las propiedades se adhieren a un patrón de nomenclatura específico. El SpecialName atributo al que se hace referencia en la regla 24 de CLS se omitirá en las comparaciones de nombres adecuadas y cumplirá las reglas de identificador. Una propiedad deberá tener un método getter, un método setter o ambos. |
28 |
| Conversión de tipos | Conversión de tipos | Si se proporciona op_Implicit o op_Explicit, se proporcionará un medio alternativo para proporcionar la coerción. | 39 |
| Tipos | Signaturas de tipos y miembros de tipo | Los tipos de valor a los que se les ha aplicado la conversión boxing no son conformes a CLS. | 3 |
| Tipos | Signaturas de tipos y miembros de tipo | Todos los tipos que aparecen en una firma deben ser compatibles con CLS (Common Language Specification). Todos los tipos que componen un tipo genérico instanciado deben cumplir con la norma CLS. | 11 |
| Tipos | Signaturas de tipos y miembros de tipo | Las referencias a tipos no son conformes a CLS. | 14 |
| Tipos | Signaturas de tipos y miembros de tipo | Los tipos de puntero no administrados no son compatibles con CLS. | 17 |
| Tipos | Signaturas de tipos y miembros de tipo | Las clases, los tipos de valor y las interfaces conformes a CLS no requerirán la implementación de miembros no conformes a CLS | 20 |
| Tipos | Signaturas de tipos y miembros de tipo | System.Object es compatible con CLS. Cualquier otra clase conforme a CLS heredará de una clase conforme a CLS. | 23 |
Índice a subsecciones:
- Signaturas de tipos y miembros de tipo
- Convenciones de nomenclatura
- Conversión de tipos
- matrices de
- Interfaces
- enumeraciones
- Miembros de tipos en general
- Accesibilidad de miembros
- Tipos y miembros genéricos
- Constructores
- Propiedades
- Eventos
- Sobrecargas
- Excepciones
- Atributos
Signaturas de tipos y miembros de tipo
El tipo System.Object es compatible con CLS y es el tipo base de todos los tipos de objetos del sistema de tipos de .NET. La herencia en .NET es implícita (por ejemplo, la clase String hereda implícitamente de la Object clase ) o explícita (por ejemplo, la clase CultureNotFoundException hereda explícitamente de la clase ArgumentException , que hereda explícitamente de la clase Exception . Para que un tipo derivado sea compatible con CLS, su tipo base también debe ser compatible con CLS.
En el ejemplo siguiente se muestra un tipo derivado cuyo tipo base no es compatible con CLS. Define una clase base Counter que usa un entero de 32 bits sin signo como contador. Dado que la clase proporciona la funcionalidad de contador ajustando un entero sin signo, la clase se marca como no conforme a CLS. Como resultado, una clase derivada, NonZeroCounter, tampoco es compatible con 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
' ~~~~~~~~~~~~~~
Todos los tipos que aparecen en las signaturas de miembros, incluidos los tipos de propiedades y los tipos de valores devueltos de un método, deben ser conformes a CLS. Además, para tipos genéricos:
Todos los tipos que componen un tipo genérico instanciado deben ser compatibles con el CLS.
Todos los tipos usados como restricciones en parámetros genéricos deben ser compatibles con CLS.
El sistema de tipos comunes de .NET incluye muchos tipos integrados que son compatibles directamente con Common Language Runtime y se codifican especialmente en los metadatos de un ensamblado. De estos tipos intrínsecos, los tipos enumerados en la tabla siguiente son compatibles con CLS.
| Tipo conforme a CLS | Descripción |
|---|---|
| byte de | Entero sin signo de 8 bits |
| Int16 | Entero de 16 bits con signo |
| Int32 | Entero de 32 bits con signo |
| Int64 | Entero de 64 bits con signo |
| Mitad | Valor de punto flotante de precisión media |
| único | Valor de punto flotante de precisión única |
| Doble | Valor de punto flotante de doble precisión |
| Booleano | Tipo de valor true o false |
| char | Unidad de código codificada UTF-16 |
| Decimal | Número decimal que no es de punto flotante |
| IntPtr | Puntero o identificador de un tamaño definido por la plataforma |
| Cuerda | Colección de cero, uno o varios objetos Char |
Los tipos intrínsecos enumerados en la tabla siguiente no son conformes a CLS.
| Tipo no compatible | Descripción | Alternativa conforme a CLS |
|---|---|---|
| SByte | Tipo de datos enteros de 8 bits con signo | Int16 |
| UInt16 | Entero sin signo de 16 bits | Int32 |
| UInt32 | Entero sin signo de 32 bits | Int64 |
| UInt64 | Entero sin signo de 64 bits | Int64 (puede desbordar), BigInteger o Double |
| UIntPtr | Puntero o identificador sin signo | IntPtr |
La biblioteca de clases de .NET o cualquier otra biblioteca de clases puede incluir otros tipos que no son compatibles con CLS, por ejemplo:
Tipos de valor a los que se les ha aplicado la conversión boxing. En el ejemplo de C# siguiente se crea una clase que tiene una propiedad pública de tipo
int*denominadaValue. Dado que esint*un tipo de valor boxed, el compilador lo marca como no compatible con 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-compliantReferencias con tipo, que son construcciones especiales que contienen una referencia a un objeto y una referencia a un tipo. Las referencias tipadas están representadas en .NET por la clase TypedReference.
Si un tipo no es conforme a CLS, deberá aplicarle el atributo CLSCompliantAttribute con el valor de isCompliant establecido en false. Para obtener más información, consulte la sección Atributo CLSCompliantAttribute .
En el ejemplo siguiente se muestra el problema del cumplimiento de CLS en una firma de método y en la instanciación de tipos genéricos. Define una InvoiceItem clase con una propiedad de tipo UInt32, una propiedad de tipo Nullable<UInt32>y un constructor con parámetros de tipo UInt32 y Nullable<UInt32>. Al intentar compilar este ejemplo, obtendrá cuatro advertencias del compilador.
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
' ~~~~~~~~~
Para eliminar las advertencias del compilador, reemplace los tipos no conformes a CLS en la InvoiceItem interfaz pública por tipos compatibles:
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
Además de los tipos específicos enumerados, algunas categorías de tipos no son compatibles con CLS. Estos incluyen tipos de puntero no administrados y tipos de puntero de función. En el ejemplo siguiente se genera una advertencia del compilador porque usa un puntero a un entero para crear una matriz de enteros.
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
Para las clases abstractas compatibles con CLS (es decir, las clases marcadas como abstract en C# o como MustInherit en Visual Basic), todos los miembros de la clase también deben ser compatibles con CLS.
Convenciones de nomenclatura
Dado que algunos lenguajes de programación distinguen entre mayúsculas y minúsculas, los identificadores (como los nombres de espacios de nombres, los tipos y los miembros) deben tener otro elemento distintivo aparte del uso de mayúsculas. Dos identificadores se consideran equivalentes si sus asignaciones en minúsculas son iguales. En el ejemplo de C# siguiente se definen dos clases públicas y Personperson. Dado que solo difieren por caso, el compilador de C# los marca como no compatibles con 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)
Los identificadores del lenguaje de programación, como los nombres de los espacios de nombres, los tipos y los miembros, deben cumplir el estándar Unicode. Esto significa que:
El primer carácter de un identificador puede ser cualquier letra mayúscula Unicode, letra minúscula, letra en título, letra modificadora, otra letra o número de letra. Para obtener información sobre las categorías de caracteres Unicode, vea la System.Globalization.UnicodeCategory enumeración .
Los caracteres siguientes pueden proceder de cualquier categoría cuando funcionan como primer carácter y también pueden incluir marcas no espaciadas, marcas de combinación de espaciado, números decimales, puntuaciones de conexión y códigos de formato.
Antes de comparar identificadores, debe filtrar los códigos de formato y convertir los identificadores al formulario de normalización Unicode C, ya que un solo carácter se puede representar mediante varias unidades de código codificadas UTF-16. Las secuencias de caracteres que producen las mismas unidades de código en el formulario de normalización Unicode C no son compatibles con CLS. En el ejemplo siguiente se define una propiedad denominada Å, que consta del carácter ANGSTROM SIGN (U+212B) y una segunda propiedad denominada Å, que consta del carácter LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5). Tanto los compiladores de C# como los compiladores de Visual Basic marcan el código fuente como no compatibles con 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
' ~
Los nombres de miembros con un ámbito determinado (como los espacios de nombres de un ensamblado, los tipos de un espacio de nombres o los miembros de un tipo) deben ser únicos, excepto los nombres que se resuelven a través de la sobrecarga. Este requisito es más estricto que el del sistema de tipos común, que permite que varios miembros dentro de un ámbito tengan nombres idénticos siempre que sean diferentes tipos de miembros (por ejemplo, uno es un método y otro es un campo). En particular, en el caso de los miembros de tipo:
Los campos y los tipos anidados se distinguen solo por nombre.
Los métodos, las propiedades y los eventos que tienen el mismo nombre deben diferir más que simplemente el tipo de valor devuelto.
En el ejemplo siguiente se muestra el requisito de que los nombres de miembro deben ser únicos dentro de su ámbito. Define una clase denominada Converter que incluye cuatro miembros denominados Conversion. Tres son métodos y uno es una propiedad. El método que incluye un Int64 parámetro se denomina de forma única, pero los dos métodos con un Int32 parámetro no son, ya que el valor devuelto no se considera parte de la firma de un miembro. La Conversion propiedad también infringe este requisito, ya que las propiedades no pueden tener el mismo nombre que los métodos sobrecargados.
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
' ~~~~~~~~~~
Los lenguajes individuales incluyen palabras clave únicas, por lo que los idiomas que tienen como destino Common Language Runtime también deben proporcionar algún mecanismo para hacer referencia a identificadores (como nombres de tipo) que coincidan con palabras clave. Por ejemplo, case es una palabra clave en C# y Visual Basic. Sin embargo, el siguiente ejemplo de Visual Basic puede desambiguar una clase denominada case de la case palabra clave mediante llaves de apertura y cierre. De lo contrario, el ejemplo generaría el mensaje de error "Keyword is not valid as an identifier" (Palabra clave no es válida como identificador) y no se pudo compilar.
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
En el siguiente ejemplo de C# se crean instancias de la clase case utilizando el símbolo @ para eliminar la ambigüedad entre el identificador y la palabra clave del lenguaje. Sin él, el compilador de C# mostraría dos mensajes de error similares a los siguientes: "Se esperaba un tipo" y "'Término 'case' de expresión no válido".
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
Conversión de tipos
Common Language Specification define dos operadores de conversión:
op_Implicit, que se utiliza para realizar conversiones más amplias que no provocan la pérdida de datos o precisión. Por ejemplo, la Decimal estructura incluye un operador sobrecargadoop_Implicitpara convertir valores de tipos enteros y valores Char a valores Decimal.op_Explicit, que se usa para restringir conversiones que pueden dar lugar a la pérdida de magnitud (un valor se convierte en un valor que tiene un intervalo más pequeño) o precisión. Por ejemplo, la estructura Decimal incluye un operador sobrecargadoop_Explicitpara convertir valores Double y Single en Decimal y para convertir valores Decimal en valores enteros, Double, Single, y Char.
Sin embargo, no todos los lenguajes admiten la sobrecarga del operador o la definición de operadores personalizados. Si decide implementar estos operadores de conversión, también debe proporcionar una manera alternativa de realizar la conversión. Nosotros recomendamos que proporciones FromXxx y ToXxx métodos.
En el ejemplo siguiente se definen conversiones implícitas y explícitas compatibles con CLS. Crea una clase que representa un UDouble número de punto flotante sin signo, de precisión doble. Proporciona conversiones implícitas de UDouble a Double y para conversiones explícitas de UDouble a Single, Double a UDoubley Single a UDouble. También define un ToDouble método como alternativa al operador de conversión implícito y los ToSinglemétodos , FromDoubley FromSingle como alternativas a los operadores de conversión explícitos.
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
Matrices
Las matrices conformes a CLS cumplen las reglas siguientes:
Todas las dimensiones de una matriz deben tener un límite inferior de cero. En el ejemplo siguiente se crea una matriz no compatible con CLS con un límite inferior de uno. A pesar de la presencia del CLSCompliantAttribute atributo , el compilador no detecta que la matriz devuelta por el
Numbers.GetTenPrimesmétodo no es compatible con 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 ClassTodos los elementos de la matriz deben componerse de tipos conformes a CLS. En el ejemplo siguiente se definen dos métodos que devuelven matrices no compatibles con CLS. La primera devuelve una matriz de UInt32 valores. El segundo devuelve una matriz Object que incluye los valores Int32 y UInt32. Aunque el compilador identifica la primera matriz como no compatible debido a su UInt32 tipo, no reconoce que la segunda matriz incluye elementos no compatibles con 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 resolución de sobrecarga de los métodos que tienen parámetros de matriz se basa en el hecho de que son matrices y en su tipo de elemento. Por este motivo, la siguiente definición de un método sobrecargado
GetSquareses compatible con 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
Las interfaces conformes a CLS pueden definir propiedades, eventos y métodos virtuales (métodos sin implementación). Una interfaz conforme a CLS no puede tener ninguno de los siguientes elementos:
Métodos estáticos o campos estáticos. Los compiladores de C# y Visual Basic generan errores del compilador si define un miembro estático en una interfaz.
Campos. Los compiladores de C# y Visual Basic generan errores del compilador si define un campo en una interfaz.
Métodos que no son compatibles con CLS. Por ejemplo, la siguiente definición de interfaz incluye un método,
INumber.GetUnsigned, que está marcado como no compatible con CLS. En este ejemplo se genera una advertencia del compilador.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 ' ~~~~~~~~~~~Debido a esta regla, no se necesitan tipos conformes a CLS para implementar miembros no conformes a CLS. Si un marco conforme a CLS expone una clase que implementa una interfaz no compatible con CLS, también debe proporcionar implementaciones concretas de todos los miembros no conformes a CLS.
Los compiladores de lenguaje compatibles con CLS también deben permitir que una clase proporcione implementaciones independientes de miembros que tengan el mismo nombre y firma en varias interfaces. Tanto C# como Visual Basic admiten implementaciones de interfaz explícitas para proporcionar diferentes implementaciones de métodos con nombre idéntico. Visual Basic también admite la Implements palabra clave , que permite designar explícitamente qué interfaz y miembro implementa un miembro determinado. En el ejemplo siguiente se muestra este escenario definiendo una Temperature clase que implementa las ICelsius interfaces y IFahrenheit como implementaciones de interfaz explícitas.
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
Enumeraciones
Las enumeraciones conformes a CLS deben seguir estas reglas:
El tipo subyacente de la enumeración debe ser un entero intrínseco compatible con CLS (Byte, Int16, Int32o Int64). Por ejemplo, el código siguiente intenta definir una enumeración cuyo tipo subyacente es UInt32 y genera una advertencia del compilador.
using System; [assembly: CLSCompliant(true)] public enum Size : uint { Unspecified = 0, XSmall = 1, Small = 2, Medium = 3, Large = 4, XLarge = 5 }; public class Clothing { public string Name; public string Type; public string Size; } // The attempt to compile the example displays a compiler warning like the following: // Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant<Assembly: CLSCompliant(True)> Public Enum Size As UInt32 Unspecified = 0 XSmall = 1 Small = 2 Medium = 3 Large = 4 XLarge = 5 End Enum Public Class Clothing Public Name As String Public Type As String Public Size As Size End Class ' The attempt to compile the example displays a compiler warning like the following: ' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant. ' ' Public Enum Size As UInt32 ' ~~~~Un tipo de enumeración debe tener un único campo de instancia denominado
Value__que esté marcado con el FieldAttributes.RTSpecialName atributo . Esto le permite hacer referencia al valor de campo implícitamente.Una enumeración incluye campos estáticos literales cuyos tipos coinciden con el tipo de la propia enumeración. Por ejemplo, si define una
Stateenumeración con valores deState.OnyState.Off,State.OnyState.Offson campos estáticos literales cuyo tipo esState.Hay dos tipos de enumeraciones:
Enumeración que representa un conjunto de valores enteros con nombre, excluyentes entre sí. Este tipo de enumeración se indica mediante la ausencia del System.FlagsAttribute atributo personalizado.
Enumeración que representa un conjunto de marcas de bits que se pueden combinar para generar un valor sin nombre. Este tipo de enumeración se indica mediante la presencia del System.FlagsAttribute atributo personalizado.
Para obtener más información, consulte la documentación de la Enum estructura.
El valor de una enumeración no se limita al intervalo de sus valores especificados. Es decir, el intervalo de valores de una enumeración es el intervalo de su valor subyacente. Puede usar el Enum.IsDefined método para determinar si un valor especificado es miembro de una enumeración.
Miembros de tipos en general
Common Language Specification requiere que se acceda a todos los campos y métodos como miembros de una clase determinada. Por lo tanto, los campos y métodos estáticos globales (es decir, los campos estáticos o los métodos definidos aparte de un tipo) no son compatibles con CLS. Si intenta incluir un campo o método global en el código fuente, los compiladores de C# y Visual Basic generan un error del compilador.
Common Language Specification solo admite la convención de llamada administrada estándar. No admite convenciones y métodos de llamada no administrados con listas de argumentos variables marcadas con la varargs palabra clave . Para las listas de argumentos variables compatibles con la convención de llamada administrada estándar, use el ParamArrayAttribute atributo o la implementación del lenguaje individual, como la params palabra clave en C# y la ParamArray palabra clave en Visual Basic.
Accesibilidad de miembros
Al reemplazar un miembro heredado no se puede cambiar la accesibilidad de dicho miembro. Por ejemplo, un método público en una clase base no se puede invalidar mediante un método privado en una clase derivada. Hay una excepción: un miembro protected internal (en C#) o Protected Friend (en Visual Basic) de un ensamblado que es sobreescrito por un tipo en otro ensamblado. En ese caso, la accesibilidad del reemplazo es Protected.
En el ejemplo siguiente se muestra el error que se genera cuando el atributo CLSCompliantAttribute se establece en true, y Human, que es una clase derivada de Animal, intenta cambiar la accesibilidad de la propiedad Species de pública a privada. El ejemplo se compila correctamente si su accesibilidad se cambia a pública.
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
Los tipos de la firma de un miembro deben ser accesibles siempre que ese miembro sea accesible. Por ejemplo, esto significa que un miembro público no puede incluir un parámetro cuyo tipo sea privado, protegido o interno. En el ejemplo siguiente se muestra el error del compilador que se produce cuando un StringWrapper constructor de clase expone un valor de enumeración interno StringOperationType que determina cómo se debe encapsular un valor de cadena.
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)
' ~~~~~~~~~~~~~~~~~~~
Tipos y miembros genéricos
Los tipos anidados siempre tienen, como mínimo, el mismo número de parámetros genéricos que el tipo envolvente. Estos corresponden según su posición a los parámetros genéricos del tipo contenedor. El tipo genérico también puede incluir nuevos parámetros genéricos.
La relación entre los parámetros de tipo genérico de un tipo contenedor y sus tipos anidados puede ocultarse mediante la sintaxis de idiomas individuales. En el ejemplo siguiente, un tipo genérico Outer<T> contiene dos clases anidadas, Inner1A y Inner1B<U>. Las llamadas al ToString método , que cada clase hereda de Object.ToString(), muestran que cada clase anidada incluye los parámetros de tipo de su clase contenedora.
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]
Los nombres de tipo genérico se codifican en la forma name`n, donde name es el nombre de tipo, ` es un carácter literal y n es el número de parámetros declarados en el tipo o, para los tipos genéricos anidados, el número de parámetros de tipo añadidos. Esta codificación de nombres de tipos genéricos es principalmente de interés para los desarrolladores que utilizan la reflexión para acceder a tipos genéricos compatibles con CLS en una biblioteca.
Si las restricciones se aplican a un tipo genérico, los tipos usados como restricciones también deben ser compatibles con CLS. En el ejemplo siguiente se define una clase denominada BaseClass que no es compatible con CLS y una clase genérica denominada BaseCollection cuyo parámetro de tipo debe derivar de BaseClass. Pero dado que BaseClass no es compatible con CLS, el compilador emite una advertencia.
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 tipo genérico se deriva de un tipo base genérico, debe volver a declarar las restricciones para que pueda garantizar que también se cumplan las restricciones del tipo base. En el ejemplo siguiente se define un Number<T> que puede representar cualquier tipo numérico. También define una FloatingPoint<T> clase que representa un valor de punto flotante. Sin embargo, el código fuente no se puede compilar, ya que no aplica la restricción en Number<T> (que T debe ser un tipo de valor) a FloatingPoint<T>.
using System;
[assembly:CLSCompliant(true)]
public class Number<T> where T : struct
{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;
public Number(T value)
{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}
public T Add(T value)
{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}
public T Subtract(T value)
{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}
public class FloatingPoint<T> : Number<T>
{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to compile the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly: CLSCompliant(True)>
Public Class Number(Of T As Structure)
' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double
Public Sub New(value As T)
Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub
Public Function Add(value As T) As T
Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function
Public Function Subtract(value As T) As T
Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class
Public Class FloatingPoint(Of T) : Inherits Number(Of T)
Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to compile the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~
El ejemplo se compila correctamente si la restricción se agrega a la FloatingPoint<T> clase .
using System;
[assembly:CLSCompliant(true)]
public class Number<T> where T : struct
{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;
public Number(T value)
{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}
public T Add(T value)
{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}
public T Subtract(T value)
{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}
public class FloatingPoint<T> : Number<T> where T : struct
{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly: CLSCompliant(True)>
Public Class Number(Of T As Structure)
' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double
Public Sub New(value As T)
Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub
Public Function Add(value As T) As T
Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function
Public Function Subtract(value As T) As T
Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class
Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)
Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
Common Language Specification impone un modelo conservador adaptado a cada instancia en los tipos anidados y los miembros protegidos. Los tipos genéricos abiertos no pueden exponer campos o miembros con firmas que contengan una instanciación específica de un tipo genérico anidado protegido. Los tipos no genéricos que extienden una instancia específica de una clase base o interfaz genérica no pueden exponer campos o miembros con firmas que contengan una instancia diferente de un tipo genérico anidado protegido.
En el ejemplo siguiente se define un tipo genérico ( C1<T> o C1(Of T) en Visual Basic) y una clase protegida ( C1<T>.N o C1(Of T).N en Visual Basic).
C1<T> tiene dos métodos, M1 y M2. Sin embargo, M1 no es compatible con CLS porque intenta devolver un C1<int>.N objeto (o C1(Of Integer).N) de C1<T> (o C1(Of T)). Una segunda clase, C2, se deriva de C1<long> (o C1(Of Long)). Tiene dos métodos, M3 y M4.
M3 no es compatible con CLS porque intenta devolver un C1<int>.N objeto (o C1(Of Integer).N) de una subclase de C1<long>. Los compiladores de lenguaje pueden ser aún más restrictivos. En este ejemplo, Visual Basic muestra un error cuando intenta compilar 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)
' ~~~~~~~~~~~~~
Constructores
Los constructores de las estructuras y clases conformes a CLS deben seguir estas reglas:
Un constructor de una clase derivada debe llamar al constructor de instancia de su clase base antes de acceder a los datos de instancia heredados. Este requisito se debe a que los constructores de clase base no se heredan por sus clases derivadas. Esta regla no se aplica a las estructuras, que no admiten la herencia directa.
Normalmente, los compiladores aplican esta regla independientemente del cumplimiento de CLS, como se muestra en el ejemplo siguiente. Crea una
Doctorclase derivada de unaPersonclase, pero laDoctorclase no puede llamar alPersonconstructor de clase para inicializar los campos de instancia heredados.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() ' ~~~No se puede llamar a un constructor de objetos excepto para crear un objeto . Además, un objeto no se puede inicializar dos veces. Por ejemplo, esto significa que Object.MemberwiseClone y los métodos de deserialización no deben llamar a constructores.
Propiedades
Las propiedades de los tipos conformes a CLS deben seguir estas reglas:
Una propiedad debe tener un establecedor, un captador o ambos. En un ensamblado, estos se implementan como métodos especiales, lo que significa que aparecerán como métodos independientes (el captador se denomina
get_propertyname y el establecedor esset_propertyname) marcado comoSpecialNameen los metadatos del ensamblado. Los compiladores de C# y Visual Basic aplican esta regla automáticamente sin necesidad de aplicar el CLSCompliantAttribute atributo .El tipo de una propiedad es el tipo de valor devuelto del captador de la propiedad y el último argumento del establecedor. Estos tipos deben ser compatibles con CLS y los argumentos no se pueden asignar a la propiedad por referencia (es decir, no pueden ser punteros gestionados).
Si una propiedad tiene un captador y un establecedor, estos deben ser virtuales, estáticos o de instancia. Los compiladores de C# y Visual Basic aplican automáticamente esta regla a través de su sintaxis de definición de propiedad.
Eventos
Un evento se define por su nombre y su tipo. El tipo de evento es un delegado que se utiliza para indicar el evento. Por ejemplo, el AppDomain.AssemblyResolve evento es de tipo ResolveEventHandler. Además del propio evento, tres métodos con nombres basados en el nombre del evento proporcionan la implementación del evento y se marcan como SpecialName en los metadatos del ensamblado:
Método para agregar un controlador de eventos, denominado
add_EventName. Por ejemplo, el método de suscripción de eventos para el AppDomain.AssemblyResolve evento se denominaadd_AssemblyResolve.Método para quitar un controlador de eventos, denominado
remove_EventName. Por ejemplo, el método de eliminación del AppDomain.AssemblyResolve evento se denominaremove_AssemblyResolve.Método para indicar que se ha producido el evento, denominado
raise_EventName.
Nota:
La mayoría de las reglas de Common Language Specification con respecto a los eventos se implementan mediante compiladores de lenguaje y son transparentes para los desarrolladores de componentes.
Los métodos para agregar, quitar y generar el evento deben tener la misma accesibilidad. También deben ser estáticos, instancias o virtuales. Los métodos para agregar y quitar un evento tienen un parámetro cuyo tipo es el tipo de delegado de eventos. Los métodos add y remove deben estar presentes o ambos deben estar ausentes.
En el ejemplo siguiente se define una clase conforme a CLS denominada Temperature que genera un TemperatureChanged evento si el cambio de temperatura entre dos lecturas es igual o supera un valor de umbral. La Temperature clase define explícitamente un raise_TemperatureChanged método para que pueda ejecutar de forma selectiva controladores de eventos.
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
Sobrecargas
La Especificación del Lenguaje Común impone los siguientes requisitos a los miembros que están sobrecargados:
Los miembros se pueden sobrecargar en función del número de parámetros y del tipo de cualquier parámetro. A la hora de distinguir entre sobrecargas, no se tienen en cuenta los factores de convención de llamada, el tipo de valor devuelto, los modificadores personalizados aplicados al método o a su parámetro, ni si los parámetros se pasan por valor o por referencia. Para obtener un ejemplo, vea el código del requisito de que los nombres deben ser únicos dentro de un ámbito en la sección Convenciones de nomenclatura .
Solo las propiedades y los métodos se pueden sobrecargar. Los campos y eventos no se pueden sobrecargar.
Los métodos genéricos se pueden sobrecargar en función del número de sus parámetros genéricos.
Nota:
Los operadores op_Explicit y los op_Implicit son excepciones a la regla de que el valor devuelto no se considera parte de una signatura de método para la resolución de sobrecargas. Estos dos operadores se pueden sobrecargar en función de sus parámetros y su valor devuelto.
Excepciones
Los objetos de excepción deben derivar de System.Exception o de otro tipo derivado de System.Exception. En el ejemplo siguiente se muestra el error del compilador que se produce cuando se usa una clase personalizada denominada ErrorClass para el control de excepciones.
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
' ~~~~~~~~~~~~~~
Para corregir este error, la ErrorClass clase debe heredar de System.Exception. Además, la propiedad Message debe invalidarse. En el ejemplo siguiente se corrigen estos errores para definir una ErrorClass clase compatible con 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
Atributos
En los ensamblados de .NET, los atributos personalizados proporcionan un mecanismo extensible para almacenar atributos personalizados y recuperar metadatos sobre objetos de programación, como ensamblados, tipos, miembros y parámetros de método. Los atributos personalizados deben derivar de System.Attribute o de un tipo derivado de System.Attribute.
En el ejemplo siguiente se infringe esta regla. Define una NumericAttribute clase que no deriva de System.Attribute. Un error del compilador solo se produce cuando se aplica el atributo no conforme a CLS, no cuando se define la clase.
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
' ~~~~~~~~~~~~~
El constructor o las propiedades de un atributo conforme a CLS solo pueden exponer los siguientes tipos:
En el ejemplo siguiente se define una DescriptionAttribute clase que deriva de Attribute. El constructor de clase tiene un parámetro de tipo Descriptor, por lo que la clase no es compatible con CLS. El compilador de C# emite una advertencia pero se compila correctamente, mientras que el compilador de Visual Basic no emite una advertencia ni un error.
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
El atributo CLSCompliantAttribute
El atributo CLSCompliantAttribute se usa para indicar si un elemento de programa cumple con common Language Specification. El CLSCompliantAttribute(Boolean) constructor incluye un único parámetro obligatorio, isCompliant, que indica si el elemento de programa es compatible con CLS.
En tiempo de compilación, el compilador detecta elementos no compatibles que se supone que son compatibles con CLS y emite una advertencia. El compilador no emite advertencias para tipos o miembros que se declaran explícitamente como no compatibles.
Los desarrolladores de componentes pueden usar el CLSCompliantAttribute atributo de dos maneras:
Para definir las partes de la interfaz pública expuestas por un componente compatible con CLS y las partes que no son compatibles con CLS. Cuando el atributo se usa para marcar elementos de programa concretos como conformes a CLS, su uso garantiza que esos elementos son accesibles desde todos los lenguajes y herramientas que tienen como destino .NET.
Para asegurarse de que la interfaz pública de la biblioteca de componentes expone solo los elementos de programa compatibles con CLS. Si los elementos no son compatibles con CLS, los compiladores generalmente emitirán una advertencia.
Advertencia
En algunos casos, los compiladores de lenguaje aplican reglas compatibles con CLS independientemente de si se usa el CLSCompliantAttribute atributo . Por ejemplo, definir un miembro estático en una interfaz infringe una regla CLS. A este respecto, si define un static miembro (en C#) o Shared (en Visual Basic) en una interfaz, los compiladores de C# y Visual Basic muestran un mensaje de error y no pueden compilar la aplicación.
El CLSCompliantAttribute atributo se marca con un AttributeUsageAttribute atributo que tiene un valor de AttributeTargets.All. Este valor permite aplicar el CLSCompliantAttribute atributo a cualquier elemento de programa, incluidos ensamblados, módulos, tipos (clases, estructuras, enumeraciones, interfaces y delegados), miembros de tipo (constructores, métodos, propiedades, campos y eventos), parámetros, parámetros genéricos y valores devueltos. Sin embargo, en la práctica, solo debe aplicar el atributo a los ensamblados, tipos y miembros del tipo. De lo contrario, los compiladores omiten el atributo y continúan generando advertencias del compilador siempre que encuentren un parámetro no compatible, un parámetro genérico o un valor devuelto en la interfaz pública de la biblioteca.
El valor del CLSCompliantAttribute atributo se hereda por elementos de programa contenidos. Por ejemplo, si un ensamblado está marcado como conforme a CLS, sus tipos también son conformes a CLS. Si un tipo está marcado como compatible con CLS, sus tipos internos y miembros también son compatibles con CLS.
Puede invalidar explícitamente el cumplimiento heredado aplicando el CLSCompliantAttribute atributo a un elemento de programa contenido. Por ejemplo, puede usar el CLSCompliantAttribute atributo con un isCompliant valor de false para definir un tipo no compatible en un ensamblado compatible y puede usar el atributo con un isCompliant valor de true para definir un tipo conforme en un ensamblado no compatible. También puede definir miembros no conformes en un tipo conforme. Sin embargo, un tipo no compatible no puede tener miembros compatibles, por lo que no puede usar el atributo con un isCompliant valor de true para invalidar la herencia de un tipo no compatible.
Al desarrollar componentes, siempre debe usar el CLSCompliantAttribute atributo para indicar si el ensamblado, sus tipos y sus miembros son compatibles con CLS.
Para crear componentes compatibles con CLS:
Utiliza el CLSCompliantAttribute para marcar tu ensamblado como compatible con CLS.
Marque los tipos expuestos públicamente en el ensamblado que no son compatibles con CLS como no compatibles.
Marque como no conforme los miembros expuestos públicamente en tipos conformes a CLS.
Proporcione una alternativa conforme a CLS para los miembros que no sean conformes a CLS.
Si ha marcado correctamente todos los tipos y miembros no compatibles, el compilador no debe emitir ninguna advertencia de incumplimiento. Sin embargo, debe indicar qué miembros no son conformes a CLS y mostrar las alternativas conformes a CLS en la documentación del producto.
En el ejemplo siguiente se usa el CLSCompliantAttribute atributo para definir un ensamblado conforme a CLS y un tipo , CharacterUtilitiesque tiene dos miembros no conformes a CLS. Dado que ambos miembros se etiquetan con el CLSCompliant(false) atributo , el compilador no genera ninguna advertencia. La clase también proporciona una alternativa compatible con CLS para ambos métodos. Normalmente, agregaríamos simplemente dos sobrecargas al método ToUTF16 para proporcionar alternativas compatibles con CLS. Sin embargo, dado que los métodos no se pueden sobrecargar en función del valor devuelto, los nombres de los métodos conformes a CLS son diferentes de los nombres de los métodos no compatibles.
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 estás desarrollando una aplicación en lugar de una biblioteca (es decir, si no expones tipos o miembros que otros desarrolladores de aplicaciones pueden consumir), el cumplimiento clS de los elementos de programa que consume tu aplicación solo son de interés si tu lenguaje no los admite. En ese caso, el compilador de lenguaje generará un error al intentar usar un elemento no conforme a CLS.
Interoperabilidad entre lenguajes
La independencia del lenguaje tiene algunos significados posibles. Un significado implica consumir sin problemas tipos escritos en un idioma desde una aplicación escrita en otro idioma. Un segundo significado, que es el foco de este artículo, implica combinar código escrito en varios lenguajes en un único ensamblado de .NET.
En el ejemplo siguiente se muestra la interoperabilidad entre lenguajes mediante la creación de una biblioteca de clases denominada Utilities.dll que incluye dos clases y NumericLibStringLib. La NumericLib clase se escribe en C#y la StringLib clase se escribe en Visual Basic. Este es el código fuente de StringUtil.vb, que incluye un único miembro, ToTitleCase, en su StringLib clase .
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
Este es el código fuente de NumberUtil.cs, que define una NumericLib clase que tiene dos miembros y 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;
}
}
Para empaquetar las dos clases en un único ensamblado, debe compilarlas en módulos. Para compilar el archivo de código fuente de Visual Basic en un módulo, use este comando:
vbc /t:module StringUtil.vb
Para obtener más información sobre la sintaxis de línea de comandos del compilador de Visual Basic, vea Compilar desde la línea de comandos.
Para compilar el archivo de código fuente de C# en un módulo, use este comando:
csc /t:module NumberUtil.cs
A continuación, use las opciones del enlazador para compilar los dos módulos en un ensamblado:
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
A continuación, el ejemplo llama a los métodos NumericLib.NearZero y StringLib.ToTitleCase. Tanto el código de Visual Basic como el código de C# pueden acceder a los métodos de ambas clases.
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
Para compilar el código de Visual Basic, use este comando:
vbc example.vb /r:UtilityLib.dll
Para compilar con C#, cambie el nombre del compilador de vbc a cscy cambie la extensión de archivo de .vb a .cs:
csc example.cs /r:UtilityLib.dll