Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Платформа .NET является независимой от языка. Это означает, что в качестве разработчика можно разрабатывать на одном из многих языков, предназначенных для реализаций .NET, таких как C#, F# и Visual Basic. Вы можете получить доступ к типам и членам библиотек классов, разработанных для реализации .NET, не зная языка, в котором они были написаны первоначально и не следовать каким-либо из соглашений о исходном языке. Если вы разработчик компонентов, ваш компонент может получить доступ к любому приложению .NET независимо от его языка.
Замечание
В первой части этой статьи рассматривается создание независимых от языка компонентов, то есть компонентов, которые могут использоваться приложениями, написанными на любом языке. Вы также можете создать один компонент или приложение из исходного кода, написанного на нескольких языках; См. раздел "Взаимодействие между языками " во второй части этой статьи.
Чтобы полностью взаимодействовать с другими объектами, написанными на любом языке, объекты должны предоставлять вызывающим объектам только те функции, которые являются общими для всех языков. Этот общий набор функций определяется спецификацией CLS , которая является набором правил, применяемых к созданным сборкам. Спецификация общего языка определена в разделе I, предложения 7–11 стандарта ECMA-335: инфраструктура общего языка.
Если ваш компонент соответствует спецификации общей языковой среды (CLS), он гарантированно соответствует требованиям CLS и может быть доступен из кода в сборках, написанных на любом языке программирования, поддерживающем CLS. Вы можете определить, соответствует ли компонент спецификации Common Language во время компиляции, применив атрибут CLSCompliantAttribute к исходному коду. Дополнительные сведения см. в разделе Атрибут CLSCompliantAttribute.
Правила соответствия CLS
В этом разделе рассматриваются правила для создания компонента, совместимого с CLS. Полный список правил см. в разделе Секции I, предложении 11 стандарта ECMA-335: инфраструктура общего языка.
Замечание
Спецификация Общего языка (Common Language Specification) описывает каждое правило соответствия CLS, как оно применяется к потребителям (разработчикам, которые программно обращаются к компоненту, совместимым с CLS), фреймворкам (разработчики, использующие компилятор языка для создания библиотек, совместимых с CLS), и расширяющим компонентам (разработчики, которые создают инструмент, например, компилятор языка или средство синтаксического анализа кода, которое создает компоненты, совместимые с CLS). В этой статье рассматриваются правила, применяемые к платформам. Обратите внимание, что некоторые правила, применяемые к расширениям, также могут применяться к сборкам, созданным с помощью reflection.Emit.
Для разработки компонента, независимого от языка, необходимо применить только правила соответствия CLS к общедоступному интерфейсу компонента. Частная реализация не должна соответствовать спецификации.
Это важно
Правила соответствия CLS применяются только к общедоступному интерфейсу компонента, а не к его частной реализации.
Например, целые числа без знака, отличные от Byte, не соответствуют требованиям CLS.
Person
Так как класс в следующем примере предоставляет Age
свойство типа UInt16, следующий код отображает предупреждение компилятора.
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
' ~~~
Вы можете сделать Person
класс CLS-совместимым, изменив тип Age
свойства с UInt16 на Int16, который является CLS-соответствующим 16-разрядным целым числом со знаком. Вам не нужно изменять тип частного 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
Общедоступный интерфейс библиотеки состоит из следующих элементов:
Определения общедоступных классов.
Определения общедоступных членов открытых классов и определения элементов, доступных производным классам (т. е. защищенным членам).
Параметры и возвращаемые типы общедоступных методов общедоступных классов, а также параметры и типы возвращаемых методов, доступные производным классам.
Правила соответствия CLS перечислены в следующей таблице. Текст правил взят дословно из стандарта ECMA-335: Инфраструктура общего языка, который является авторским правом Ecma International, 2012. Более подробные сведения об этих правилах приведены в следующих разделах.
Категория | Смотри | Правило | Номер правила |
---|---|---|---|
Доступность | Доступность для участников | Доступность не изменяется при переопределении унаследованных методов, за исключением случаев, когда переопределяется метод, унаследованный из другой сборки с доступностью family-or-assembly . В этом случае переопределение должно иметь доступность family . |
10 |
Доступность | Доступность для участников | Видимость и доступность типов и членов должны быть такими, чтобы типы в сигнатуре любого члена были видимыми и доступными, всякий раз когда сам член виден и доступен. Например, открытый метод, видимый вне сборки, не должен иметь аргумент, тип которого отображается только в сборке. Видимость и доступность типов, составляющих экземпляр универсального типа, используемого в сигнатуре любого члена, должны быть видимыми и доступными всякий раз, когда сам элемент виден и доступен. Например, экземпляр универсального типа, присутствующего в сигнатуре члена, видимого вне сборки, не должен иметь универсальный аргумент, тип которого виден только внутри сборки. | 12 |
Массивы | Массивы | Массивы должны иметь элементы с типом, совместимым с CLS, и все измерения массива должны иметь нижние границы нуля. Для различения перегрузок требуется только тот факт, что элемент является массивом, и тип элементов этого массива. При перегрузке на основе двух или более типов массивов типы элементов должны быть именованы. | 16 |
Атрибуты | Атрибуты | Атрибуты должны иметь тип System.Attributeили тип, наследующий от него. | 41 |
Атрибуты | Атрибуты | Среда CLS разрешает только подмножество кодировок пользовательских атрибутов. Единственными типами, которые должны отображаться в этих кодировках, являются (см. раздел IV): System.Type, System.String, System.Char, System.Boolean, System.Byte, System.Int16, System.Int32, System.Int64, System.Single, System.Double, и любой тип перечисления, основанный на базовом целочисленном типе, совместимом с CLS. | 34 |
Атрибуты | Атрибуты | CLS не разрешает общедоступные обязательные модификаторы (modreq , см. раздел II), но разрешает необязательные модификаторы (modopt , см. раздел II), которые он не понимает. |
35 |
Конструкторы | Конструкторы | Конструктор должен вызывать какой-либо экземплярный конструктор базового класса перед тем как будет осуществлен доступ к унаследованным данным экземпляра. (Это не относится к типам значений, которые не должны иметь конструкторов.) | двадцать один |
Конструкторы | Конструкторы | Конструктор объектов не должен вызываться, кроме как часть создания объекта, и объект не должен инициализироваться дважды. | двадцать два |
Перечисления | перечисления | Базовый тип перечисления должен быть встроенным целым типом CLS, имя поля должно быть "value__", и это поле должно быть отмечено RTSpecialName . |
7 |
Перечисления | перечисления | Существует два различных типа перечислений, обозначаемых наличием или отсутствием настраиваемого атрибута System.FlagsAttribute (см. библиотеку Секции IV). Один представляет именованные целочисленные значения; другой представляет именованные битовые флаги, которые можно объединить для создания неназванного значения. Значение enum не ограничивается указанными значениями. |
8 |
Перечисления | перечисления | Литеральные статические поля перечисления должны иметь тот же тип, что и само перечисление. | 9 |
События | События | Методы, реализующие событие, должны быть помечены SpecialName в метаданных. |
29 |
События | События | Доступность события и его методов доступа должна быть идентична. | 30 |
События | События | Методы add и remove для события должны либо присутствовать, либо отсутствовать. |
31 |
События | События | Методы add и remove для события должны принимать один параметр, тип которого определяет тип события и который должен быть производным от System.Delegate. |
32 |
События | События | События должны соответствовать определенному шаблону именования. Атрибут SpecialName, указанный в правиле CLS 29, должен игнорироваться в соответствующих сравнениях имен и должен соответствовать правилам идентификатора. | 33 |
Исключения | Исключения | Бросаемые объекты должны быть типа System.Exception или типа, наследуемого от него. Тем не менее, методы, совместимые с CLS, не требуются для блокировки распространения других типов исключений. | 40 |
Общая информация | Правила соответствия CLS | Правила CLS применяются только к тем частям типа, которые доступны или видимы вне определяемой сборки. | 1 |
Общая информация | Правила соответствия CLS | Члены неКЛS-совместимых типов не должны быть помечены как CLS-совместимые. | 2 |
Обобщенные понятия | Универсальные типы и члены | Вложенные типы должны иметь по крайней мере столько универсальных параметров, сколько и охватывающий тип. Универсальные параметры в вложенном типе по позиции соответствуют универсальным параметрам в его вмещающем типе. | 42 |
Обобщенные понятия | Универсальные типы и члены | Имя универсального типа должно кодировать число параметров типа, объявленных в не вложенном типе, или недавно введённых в тип при вложенности, согласно правилам, определённым выше. | 43 |
Обобщенные понятия | Универсальные типы и члены | Универсальный тип должен повторно указывать достаточные ограничения, чтобы гарантировать, что все ограничения базового типа или интерфейсы будут удовлетворены ограничениями универсального типа. | 44 |
Обобщенные понятия | Универсальные типы и члены | Типы, используемые в качестве ограничений для универсальных параметров, должны быть clS-совместимыми. | 45 |
Обобщенные понятия | Универсальные типы и члены | Видимость и доступность членов (включая вложенные типы) в экземпляре универсального типа должны рассматриваться как относящиеся к конкретному экземпляру, а не к объявлению универсального типа в целом. Предполагая это, правила доступности и доступа правила CLS 12 по-прежнему применяются. | 46 |
Обобщенные понятия | Универсальные типы и члены | Для каждого абстрактного или виртуального универсального метода должна быть конкретная реализация по умолчанию (nonabstract) | 47 |
Интерфейсы | Интерфейсы | Интерфейсы, совместимые с CLS, не должны требовать определения методов, не совместимых с CLS, для их реализации. | 18 |
Интерфейсы | Интерфейсы | Интерфейсы, совместимые с CLS, не должны определять статические методы и не определять поля. | 19 |
Члены | Общие элементы типа | Глобальные статические поля и методы не соответствуют CLS. | 36 |
Члены | -- | Значение литерального статического определяется с помощью метаданных инициализации полей. Литерал, совместимый с CLS, должен иметь значение, указанное в метаданных инициализации поля, которое имеет точно тот же тип, что и литерал (или базовый тип, если это литерал).enum |
13 (тринадцать) |
Члены | Общие элементы типа | Ограничение vararg не является частью среды CLS, а единственным соглашением о вызовах, поддерживаемым clS, является стандартное соглашение об управляемых вызовах. | 15 |
Соглашения об именах | Соглашения об именовании | Сборки должны следовать приложению 7 Технического отчета 15 стандарта Юникода 3.0, который регулирует набор символов, разрешенных для начала и включения в идентификаторы, доступный в Интернете на Формах нормализации Юникода. Идентификаторы должны быть в каноническом формате, определенном формой нормализации Unicode Form C. Для целей CLS два идентификатора считаются одинаковыми, если их преобразования в нижний регистр (как указано в нечувствительных к языковым особенностям однозначных сопоставлениях нижнего регистра Unicode) совпадают. То есть для двух идентификаторов, которые следует считать разными в среде CLS, они должны отличаться в большей мере, чем просто в их случае. Однако для переопределения унаследованного определения CLI требуется использование точного кодирования исходного объявления. | 4 |
Перегрузка | Соглашения об именовании | Все имена, представленные в области, совместимой с CLS, должны отличаться независимо от вида, за исключением случаев, когда имена идентичны и разрешаются за счёт перегрузки. То есть, в то время как CTS позволяет одному типу использовать то же имя для метода и поля, CLS не делает этого. | 5 |
Перегрузка | Соглашения об именовании | Поля и вложенные типы должны отличаться только по сравнению идентификаторов, несмотря на то, что CTS позволяет различать отдельные подписи. Методы, свойства и события, имеющие одинаковое имя (основанное на сравнение идентификатора), должны отличаться не только по типу возвращаемого значения, за исключением случаев, оговоренных в правиле CLS 39. | 6 |
Перегрузка | перегрузки | Можно перегружать только свойства и методы. | 37 |
Перегрузка | перегрузки | Свойства и методы могут быть перегружены только на основе числа и типов их параметров, за исключением операторов преобразования, именованных op_Implicit и op_Explicit которые также могут быть перегружены на основе их возвращаемого типа. |
38 |
Перегрузка | -- | Если два или более CLS-совместимых методов, объявленных в типе, для определенного набора инстанцирований типов имеют одинаковое имя, одинаковые параметры и возвращаемые типы, то все эти методы должны быть семантически эквивалентны в этих инстанцированиях типов. | 48 |
Свойства | Свойства | Методы, реализующие методы получения и задания свойства, должны быть помечены SpecialName в метаданных. |
двадцать четыре |
Свойства | Свойства | Методы доступа свойства должны быть статическими, виртуальными или экземплярами. | 26 |
Свойства | Свойства | Тип свойства должен быть возвращаемым типом геттера и типом последнего аргумента сеттера. Типы параметров свойства должны совпадать с типами параметров геттера и всех типов сеттера, кроме последнего параметра. Все эти типы должны соответствовать стандарту CLS и не должны быть управляемыми ссылками (т. е. не должны передаваться по ссылке). | двадцать семь |
Свойства | Свойства | Свойства должны соответствовать определенному шаблону именования. Атрибут SpecialName , упомянутый в правиле CLS 24, должен игнорироваться в соответствующих сравнениях имен и должен соответствовать правилам идентификатора. Свойство должно иметь метод получения, метод задания или оба. |
28 |
Преобразование типов | преобразования типов | Если предоставляется op_Implicit или op_Explicit, следует предоставить альтернативный способ осуществления приведения. | 39 |
Типы | Типы и сигнатуры элементов типа | Типы упакованных значений не соответствуют CLS. | 3 |
Типы | Типы и сигнатуры элементов типа | Все типы, указанные в сигнатуре, должны быть в соответствии с требованиями CLS. Все типы, составляющие экземпляр универсального типа, должны соответствовать CLS. | 11 |
Типы | Типы и сигнатуры элементов типа | Типизированные ссылки не соответствуют CLS. | 14 |
Типы | Типы и сигнатуры элементов типа | Неуправляемые типы указателей не соответствуют CLS. | 17 |
Типы | Типы и сигнатуры элементов типа | Классы, типы значений и интерфейсы, совместимые с CLS, не должны требовать реализации несоответствующих CLS-членов. | 20 |
Типы | Типы и сигнатуры элементов типа | System.Object соответствует CLS. Любой другой класс, совместимый с CLS, должен наследоваться от класса, совместимого с CLS. | двадцать три |
Индекс подразделов
- Типы и сигнатуры элементов типа
- Соглашения об именовании
- преобразования типов
- Массивы
- Интерфейсы
- перечисления
- Общие элементы типа
- Доступность для участников
- Универсальные типы и члены
- Конструкторы
- Свойства
- События
- перегрузки
- Исключения
- Атрибуты
Типы и подписи членов типа
Тип System.Object соответствует CLS и является базовым типом всех типов объектов в системе типов .NET. Наследование в .NET является неявным (например, класс String неявно наследуется от Object
класса) или явным (например, класс CultureNotFoundException явно наследует от класса ArgumentException , который явно наследует от класса Exception . Для производного типа, соответствующего CLS, его базовый тип также должен быть clS-совместимым.
В следующем примере показан производный тип, базовый тип которого не соответствует CLS. Он определяет базовый Counter
класс, использующий 32-разрядное целое число без знака в качестве счетчика. Поскольку класс предоставляет функциональность счетчика путем упаковки целого числа без знака, класс помечается как несоответствующий общему языку спецификаций (CLS). В результате производный класс NonZeroCounter
также не соответствует 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
' ~~~~~~~~~~~~~~
Все типы, которые отображаются в подписях членов, включая возвращаемый тип метода или тип свойства, должны соответствовать CLS. Кроме того, для универсальных типов:
Все типы, составляющие универсальный тип в его экземплярной форме, должны быть совместимы с CLS.
Все типы, используемые в качестве ограничений для универсальных параметров, должны быть clS-совместимыми.
Система общего типа .NET включает множество встроенных типов, которые поддерживаются непосредственно средой CLR и специально кодируются в метаданных сборки. Из этих встроенных типов типы, перечисленные в следующей таблице, соответствуют CLS.
Тип, совместимый с CLS | Описание |
---|---|
Байт | 8-разрядное целое число без знака |
Int16 | 16-разрядное целое число со знаком |
Инт32 | 32-разрядное целое число со знаком |
Int64 | 64-разрядное целое число со знаком |
Половина | Число с плавающей запятой полупроизвольной точности |
Один | Значение с плавающей запятой одинарной точности |
Двойной | Значение с плавающей запятой двойной точности |
Булев | Тип значения true или false |
Уголь | Единица кода в кодировке UTF-16 |
Десятичное число | Десятичное число без плавающей запятой |
IntPtr | Указатель или дескриптор размера, определенного платформой |
Струна | Коллекция нулевых, одного или нескольких объектов Char |
Встроенные типы, перечисленные в следующей таблице, не соответствуют CLS.
Несоответствующий тип | Описание | Альтернативный вариант, совместимый с CLS |
---|---|---|
SByte | 8-разрядный целочисленный тип данных со знаком | Int16 |
UInt16 | 16-разрядное целое число без знака | Инт32 |
УИнт32 | 32-разрядное целое число без знака | Int64 |
УИнт64 | 64-разрядное целое число без знака | Int64 (может привести к переполнению), BigInteger или Double |
UIntPtr | Неподписанный указатель или дескриптор | IntPtr |
Библиотека классов .NET или любая другая библиотека классов могут включать другие типы, которые не соответствуют CLS, например:
Типы значений в поле. В следующем примере C# создается класс, имеющий открытое свойство типа
int*
с именемValue
. Так какint*
– это упакованный тип значения, компилятор помечает его как несовместимый с CLS.using System; [assembly:CLSCompliant(true)] public unsafe class TestClass { private int* val; public TestClass(int number) { val = (int*) number; } public int* Value { get { return val; } } } // The compiler generates the following output when compiling this example: // warning CS3003: Type of 'TestClass.Value' is not CLS-compliant
Типизированные ссылки, которые представляют собой специальные конструкции, содержащие ссылку на объект и ссылку на тип. Типизированные ссылки представлены в .NET классом TypedReference .
Если тип не соответствует CLS, следует применить CLSCompliantAttribute атрибут со значением isCompliant
false
к нему. Дополнительные сведения см. в разделе атрибута CLSCompliantAttribute .
В следующем примере демонстрируется проблема соответствия CLS в сигнатуре метода и инстанцировании универсального типа. Он определяет класс InvoiceItem
, с двумя свойствами: одно типа UInt32, другое типа Nullable<UInt32>
, и конструктором с параметрами типа UInt32 и Nullable<UInt32>
. При попытке скомпилировать этот пример вы получите четыре предупреждения компилятора.
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
' ~~~~~~~~~
Чтобы устранить предупреждения компилятора, замените типы, не совместимые с CLS, в общедоступном интерфейсе InvoiceItem
соответствующими типами:
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
Помимо указанных типов некоторые категории типов не соответствуют CLS. К ним относятся неуправляемые типы указателей и типы указателей функций. В следующем примере создается предупреждение компилятора, так как он использует указатель на целое число для создания массива целых чисел.
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
Для тех абстрактных классов, которые совместимы с CLS (например, классов, помеченных как abstract
в C# или как MustInherit
в Visual Basic), все члены класса также должны быть совместимы с CLS.
Соглашения об именах
Поскольку некоторые языки программирования не зависят от регистра, идентификаторы (например, имена пространств имен, типов и членов) должны отличаться более чем по регистру. Два идентификатора считаются эквивалентными, если их нижние регистры совпадают. В следующем примере C# определены два открытых класса Person
и person
. Так как они различаются только регистром, компилятор C# помечает их как несовместимые с 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)
Идентификаторы языка программирования, такие как имена пространств имен, типов и членов, должны соответствовать стандарту Юникода. Это означает, что:
Первый символ идентификатора может быть любой прописной буквой Юникода, строчной буквой, буквой заголовка, модификатором, другими буквами или номером буквы. Сведения о категориях символов Юникода см. в System.Globalization.UnicodeCategory перечислении.
Последующие символы могут быть из любых категорий, как и первый символ, и также могут включать незнаковые символы, знаки, объединяющие пробелы, десятичные числа, соединительную пунктуацию и коды форматирования.
Перед сравнением идентификаторов следует отфильтровать коды форматирования и преобразовать идентификаторы в форму Нормализации Юникода C, так как один символ может быть представлен несколькими единицами кода в кодировке UTF-16. Последовательности символов, которые создают те же единицы кода в форме нормализации Юникода C, не соответствуют CLS. В следующем примере определяется свойство с именем Å
, которое состоит из символа ANGSTROM SIGN (U+212B) и второго свойства с именем Å
, состоящего из символа LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5). Компиляторы C# и Visual Basic помечают исходный код как несовместимый с 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
' ~
Имена членов в определенной области (например, пространства имен в сборке, типы в пространстве имен или члены в типе) должны быть уникальными, за исключением имен, разрешенных путем перегрузки. Это требование является более строгим, чем в общей системе типов, которая позволяет нескольким членам в области иметь одинаковые имена, если они являются разными типами членов (например, один является методом, а другой — полем). В частности, для элементов типа:
Поля и вложенные типы отличаются только по имени.
Методы, свойства и события с одинаковым именем должны отличаться не только по типу возвращаемого значения.
В следующем примере показано требование, что имена элементов должны быть уникальными в пределах своей области. Он определяет класс с именем Converter
, который включает в себя четыре члена с именем Conversion
. Три являются методами, а один — свойством. Метод, включающий Int64 параметр, однозначно называется, но два метода с Int32 параметром не являются, так как возвращаемое значение не считается частью сигнатуры члена. Свойство Conversion
также нарушает это требование, так как свойства не могут иметь то же имя, что и перегруженные методы.
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
' ~~~~~~~~~~
Отдельные языки включают уникальные ключевые слова, поэтому языки, предназначенные для среды CLR, также должны предоставлять некоторый механизм для ссылок идентификаторов (таких как имена типов), которые совпадают с ключевыми словами. Например, case
ключевое слово в C# и Visual Basic. Однако следующий пример Visual Basic может разграничивать класс с именем case
от ключевого слова case
с помощью открывающих и закрывающих фигурных скобок. В противном случае в примере будет получено сообщение об ошибке "Ключевое слово недопустимо в качестве идентификатора" и не удалось выполнить компиляцию.
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
В следующем примере C# можно создать экземпляр класса case
с помощью символа @
, чтобы различить идентификатор от ключевого слова языка. Без него компилятор C# будет отображать два сообщения об ошибках: "Тип ожидаемого" и "Недопустимый термин выражения "case".
using System;
public class Example
{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}
Преобразование типов
Спецификация Common Language определяет два оператора преобразования:
op_Implicit
, который используется для расширения преобразований, которые не приводят к потере данных или точности. Например, Decimal структура включает перегруженныйop_Implicit
оператор для преобразования значений целочисленных типов и Char значений в Decimal значения.op_Explicit
, который используется для сужения преобразований, которые могут привести к потере величины (значение преобразуется в значение, которое имеет меньший диапазон) или точность. Например, структура Decimal включает перегруженный операторop_Explicit
для преобразования значений Double и Single в Decimal, а также для преобразования значений Decimal в целочисленные значения, Double, Single и Char.
Однако не все языки поддерживают перегрузку операторов или определение пользовательских операторов. Если вы решили реализовать эти операторы преобразования, необходимо также предоставить альтернативный способ выполнения преобразования. Мы рекомендуем вам предоставить методы From
Xxx и To
Xxx.
В следующем примере определяются неявные и явные преобразования, совместимые с CLS. Он создает класс UDouble
, представляющий число двойной точности с плавающей запятой и без знака. Он обеспечивает неявное преобразование из UDouble
в Double и явное преобразование из UDouble
в Single, из Double в UDouble
, и из Single в UDouble
. Он также определяет метод ToDouble
как альтернативу неявным операторам преобразования и методы ToSingle
, FromDouble
, и FromSingle
в качестве альтернативы явным операторам преобразования.
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
Массивы
Массивы, совместимые с CLS, соответствуют следующим правилам:
Все размеры массива должны иметь нижнюю границу от нуля. В следующем примере создается массив, не совместимый с CLS, с нижней границей, равной единице. Несмотря на наличие атрибута CLSCompliantAttribute , компилятор не обнаруживает, что массив, возвращаемый
Numbers.GetTenPrimes
методом, не соответствует CLS.[assembly: CLSCompliant(true)] public class Numbers { public static Array GetTenPrimes() { Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1}); arr.SetValue(1, 1); arr.SetValue(2, 2); arr.SetValue(3, 3); arr.SetValue(5, 4); arr.SetValue(7, 5); arr.SetValue(11, 6); arr.SetValue(13, 7); arr.SetValue(17, 8); arr.SetValue(19, 9); arr.SetValue(23, 10); return arr; } }
<Assembly: CLSCompliant(True)> Public Class Numbers Public Shared Function GetTenPrimes() As Array Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1}) arr.SetValue(1, 1) arr.SetValue(2, 2) arr.SetValue(3, 3) arr.SetValue(5, 4) arr.SetValue(7, 5) arr.SetValue(11, 6) arr.SetValue(13, 7) arr.SetValue(17, 8) arr.SetValue(19, 9) arr.SetValue(23, 10) Return arr End Function End Class
Все элементы массива должны состоять из типов, совместимых с CLS. В следующем примере определены два метода, возвращающие массивы, не соответствующие стандартам CLS. Первый возвращает массив значений UInt32 . Второй возвращает Object массив, включающий Int32 и UInt32 значения. Хотя компилятор идентифицирует первый массив как несоответствующий из-за его UInt32 типа, он не распознает, что второй массив включает несоответствующие 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() ' ~~~~~~~~~~~~
Разрешение перегрузки для методов, имеющих параметры массива, основано на том факте, что они являются массивами и их типом элемента. Следующее определение перегруженного метода
GetSquares
по этой причине соответствует требованиям 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
Интерфейсы
Интерфейсы, совместимые с CLS, могут определять свойства, события и виртуальные методы (методы без реализации). Интерфейс, совместимый с CLS, не может иметь следующее:
Статические методы или статические поля. Компиляторы C# и Visual Basic создают ошибки компилятора при определении статического члена в интерфейсе.
Поля. Компиляторы C# и Visual Basic создают ошибки компилятора при определении поля в интерфейсе.
Методы, не совместимые с CLS. Например, следующее определение интерфейса включает метод,
INumber.GetUnsigned
помеченный как несоответствующий CLS. В этом примере создается предупреждение компилятора.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 ' ~~~~~~~~~~~
Из-за этого правила типы, совместимые с CLS, не требуются для реализации элементов, не совместимых с CLS. Если платформа, совместимая с CLS, предоставляет класс, который реализует интерфейс, несоответствующий CLS, то она должна также предоставлять конкретные реализации всех членов, не соответствующих CLS.
Компиляторы языка, совместимые с CLS, также должны разрешить классу предоставлять отдельные реализации элементов с одинаковым именем и сигнатурой в нескольких интерфейсах. C# и Visual Basic поддерживают явные реализации интерфейсов для предоставления различных реализаций идентичных именованных методов. Visual Basic также поддерживает ключевое слово Implements
, которое позволяет явно указать, какой интерфейс и элемент реализует конкретный элемент. В следующем примере показано, как определить этот сценарий, определив класс, реализующий Temperature
ICelsius
и IFahrenheit
интерфейсы как явные реализации интерфейса.
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
Перечисления
Перечисления, совместимые с CLS, должны соответствовать следующим правилам:
Базовый тип перечисления должен быть встроенным целым числом, совместимым с CLS (Byte, , Int16Int32илиInt64). Например, следующий код пытается определить перечисление, базовый тип которого является UInt32 и создает предупреждение компилятора.
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 ' ~~~~
Тип перечисления должен иметь одно поле экземпляра с именем
Value__
, помеченным атрибутом FieldAttributes.RTSpecialName . Это позволяет ссылаться на значение поля неявно.Перечисление включает литеральные статические поля, типы которых соответствуют типу самого перечисления. Например, если вы определяете
State
перечисление со значениямиState.On
иState.Off
,State.On
иState.Off
являются литеральными статическими полями, тип которых -State
.Существует два типа перечислений:
Перечисление, представляющее набор взаимоисключающих именованных целочисленных значений. Этот тип перечисления указывается отсутствием System.FlagsAttribute пользовательского атрибута.
Перечисление, представляющее набор битовых флагов, которые могут объединяться для создания неназванного значения. Этот тип перечисления указывается наличием System.FlagsAttribute пользовательского атрибута.
Дополнительные сведения см. в документации по структуре Enum.
Значение перечисления не ограничивается диапазоном указанных значений. Другими словами, диапазон значений перечисления — это диапазон его базового значения. Метод Enum.IsDefined можно использовать для определения того, является ли указанное значение членом перечисления.
Общие элементы типа
Спецификация общего языка требует доступа ко всем полям и методам в качестве членов определенного класса. Таким образом, глобальные статические поля и методы (то есть статические поля или методы, определенные отдельно от типа), не соответствуют требованиям CLS. Если вы пытаетесь включить глобальное поле или метод в исходный код, компиляторы C# и Visual Basic создают ошибку компилятора.
Спецификация Common Language поддерживает только стандартное соглашение об управляемых вызовах. Он не поддерживает неуправляемые соглашения о вызовах и методы со списками аргументов переменной, помеченными ключевым словом varargs
. Для списков аргументов переменной, совместимых со стандартным соглашением об управляемых вызовах, используйте ParamArrayAttribute атрибут или реализацию отдельного языка, например params
ключевое слово в C# и ParamArray
ключевое слово в Visual Basic.
Доступность членов
Переопределение унаследованного элемента не может изменить специальные возможности этого элемента. Например, общедоступный метод в базовом классе нельзя переопределить частным методом в производном классе. Существует одно исключение: элемент protected internal
(в C#) или Protected Friend
(в Visual Basic) в одной сборке, который переопределен типом в другой сборке. В этом случае доступность переопределения имеет значение Protected
.
В следующем примере показано сообщение об ошибке, которое возникает, если атрибуту CLSCompliantAttribute присвоено true
, и класс Human
, производный от класса Animal
, пытается изменить доступность свойства Species
с общедоступного на частное. Пример успешно компилируется, если его доступность изменяется на общедоступную.
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
Типы в сигнатуре члена должны быть доступны всякий раз, когда этот элемент доступен. Например, это означает, что общедоступный член не может включать параметр, тип которого является частным, защищенным или внутренним. В следующем примере показана ошибка компилятора, которая приводит к тому, что StringWrapper
конструктор класса предоставляет внутреннее StringOperationType
значение перечисления, определяющее, как следует упаковать строковое значение.
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)
' ~~~~~~~~~~~~~~~~~~~
Универсальные типы и члены
Вложенные типы всегда имеют по крайней мере столько универсальных параметров, сколько их окружающий тип. Они соответствуют по позиции общим параметрам в обрамляющем типе. Универсальный тип также может включать новые универсальные параметры.
Связь между параметрами универсального типа содержащего типа и вложенными типами может быть скрыта синтаксисом отдельных языков. В следующем примере универсальный тип Outer<T>
содержит два вложенных класса Inner1A
и Inner1B<U>
. Вызовы метода ToString
, который наследуется каждым классом из Object.ToString(), показывают, что каждый вложенный класс включает типовые параметры своего содержащего класса.
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]
Имена обобщённых типов кодируются в форме имя`n, где имя — это имя типа, '`' является символом, а n — это количество параметров, объявленных для типа, или, для вложенных обобщённых типов, количество вновь введённых параметров типа. Эта кодировка имен универсальных типов в основном интересна разработчикам, использующим отражение для доступа к универсальным типам CLS-жалоб в библиотеке.
Если ограничения применяются к универсальному типу, все типы, используемые в качестве ограничений, также должны быть совместимыми с CLS. В следующем примере определяется класс с именем BaseClass
, который не соответствует CLS, и универсальный класс с именем BaseCollection
, параметр типа которого должен быть производным от BaseClass
. Но так как BaseClass
не соответствует CLS, компилятор выдает предупреждение.
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)
' ~~~~~~~~~
Если универсальный тип является производным от универсального базового типа, он должен повторно указывать все ограничения, чтобы гарантировать, что ограничения базового типа также удовлетворены. В следующем примере определяется, Number<T>
который может представлять любой числовой тип. Он также определяет FloatingPoint<T>
класс, представляющий значение с плавающей запятой. Однако исходный код не удаётся скомпилировать, так как ограничение на Number<T>
(что T должен быть типом значения) не применяется к 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)
' ~
Пример успешно компилируется, если ограничение добавляется в 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> 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
Спецификация общего языка импозирует консервативную модель инстанцирования для вложенных типов и защищенных членов. Открытые универсальные типы не могут предоставлять поля или элементы с сигнатурами, содержащими определенный экземпляр вложенного защищенного универсального типа. Типы, не являющиеся универсальными, но расширяющие конкретный экземпляр универсального базового класса или интерфейса, не могут предоставлять поля или члены с сигнатурами, содержащими другой экземпляр вложенного, защищенного универсального типа.
В следующем примере определяется универсальный тип C1<T>
(или C1(Of T)
в Visual Basic) и защищенный класс C1<T>.N
(или C1(Of T).N
в Visual Basic).
C1<T>
имеет два метода и M1
M2
. Однако M1
не соответствует требованиям CLS, так как пытается вернуть объект C1<int>.N
(или C1(Of Integer).N
) из C1<T> (или C1(Of T)
). Второй класс, C2
производный от C1<long>
(или C1(Of Long)
). Он имеет два метода: M3
и M4
.
M3
не соответствует clS, так как он пытается вернуть C1<int>.N
объект (или C1(Of Integer).N
) из подкласса C1<long>
. Компиляторы языка могут быть еще более строгими. В этом примере Visual Basic отображает ошибку при попытке компиляции 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)
' ~~~~~~~~~~~~~
Конструкторы
Конструкторы в классах и структурах, совместимых с CLS, должны соответствовать следующим правилам:
Конструктор производного класса должен вызывать конструктор экземпляра базового класса, прежде чем он обращается к унаследованным данным экземпляра. Это требование обусловлено тем, что конструкторы базового класса не наследуются производными классами. Это правило не применяется к структурам, которые не поддерживают прямое наследование.
Как правило, компиляторы применяют это правило независимо от соответствия CLS, как показано в следующем примере. Он создает класс, производный от класса
Doctor
, но классPerson
не вызывает конструктор классаDoctor
с целью инициализации унаследованных полей экземпляра.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() ' ~~~
Конструктор объектов не может вызываться, кроме создания объекта. Кроме того, объект нельзя инициализировать дважды. Например, это означает, что методы десериализации, такие как Object.MemberwiseClone, не должны вызывать конструкторы.
Свойства
Свойства в типах, совместимых с CLS, должны соответствовать следующим правилам:
Свойство должно иметь сеттер, геттер или оба. В сборке они реализуются как специальные методы, что означает, что они будут отображаться как отдельные методы (метод получения значения называется
get_
свойства, а метод установки значения —set_
свойства), помеченные какSpecialName
в метаданных сборки. Компиляторы C# и Visual Basic автоматически применяют это правило без необходимости применять CLSCompliantAttribute атрибут.Тип свойства — это возвращаемый тип геттера свойства и последний аргумент сеттера. Эти типы должны быть совместимыми с CLS, и аргументы не могут быть назначены свойству по ссылке (т. е. они не могут быть управляемыми указателями).
Если свойство имеет как метод получения, так и метод задания, они должны быть виртуальными, как статическими, так и обоими экземплярами. Компиляторы C# и Visual Basic автоматически применяют это правило с помощью синтаксиса определения свойств.
События
Событие определяется его именем и типом. Тип события — это делегат, используемый для обозначения события. Например, AppDomain.AssemblyResolve событие имеет тип ResolveEventHandler. Помимо самого события, три метода с именами на основе имени события предоставляют реализацию события и помечаются как SpecialName
в метаданных сборки:
Метод добавления обработчика событий с именем
add_
EventName. Например, метод AppDomain.AssemblyResolve подписки на событие называетсяadd_AssemblyResolve
.Метод удаления обработчика событий с именем
remove_
EventName. Например, метод удаления для AppDomain.AssemblyResolve события называетсяremove_AssemblyResolve
.Метод, указывающий, что произошло событие с именем
raise_
EventName.
Замечание
Большинство правил спецификации общего языка относительно событий реализуются компиляторами языка и прозрачны для разработчиков компонентов.
Методы добавления, удаления и вызова события должны иметь одинаковую доступность. Они также должны быть либо статическими, либо экземплярами, либо виртуальными. Методы добавления и удаления события имеют один параметр, тип которого является типом делегата события. Методы добавления и удаления должны присутствовать или оба отсутствуют.
В следующем примере определяется класс, совместимый с CLS, который вызывает событие Temperature
, если изменение температуры между двумя измерениями равно или превышает пороговое значение. Класс Temperature
явно определяет raise_TemperatureChanged
метод, чтобы он смог выборочно выполнять обработчики событий.
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
Перегрузки
Спецификация общего языка накладывает следующие требования к перегруженным членам:
Члены могут быть перегружены на основе количества параметров и типа любого параметра. Соглашение о вызовах, возвращаемый тип, настраиваемые модификаторы, применяемые к методу или его параметру, а также передаются ли параметры по значению или по ссылке, не учитываются при различении перегрузок. Пример см. в коде требования о том, что имена должны быть уникальными в пределах области в разделе соглашений об именовании .
Можно перегружать только свойства и методы. Поля и события нельзя перегружать.
Универсальные методы можно перегружать на основе числа их универсальных параметров.
Замечание
Операторы op_Explicit
и op_Implicit
являются исключениями из правила, согласно которому возвращаемое значение не считается частью сигнатуры метода для разрешения перегрузки. Эти два оператора могут быть перегружены на основе их параметров и их возвращаемого значения.
Исключения
Объекты исключений должны быть производными от System.Exception или другого типа, производного от System.Exception. В следующем примере показана ошибка компилятора, которая приводит к тому, что для обработки исключений используется пользовательский класс ErrorClass
.
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
' ~~~~~~~~~~~~~~
Чтобы исправить эту ошибку, ErrorClass
класс должен наследоваться от System.Exception. Кроме того, свойство Message
должно быть переопределено. В следующем примере эти ошибки исправляются, чтобы определить класс, совместимый ErrorClass
с 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
Атрибуты
В сборках .NET настраиваемые атрибуты предоставляют расширяемый механизм хранения пользовательских атрибутов и получения метаданных о объектах программирования, таких как сборки, типы, члены и параметры метода. Пользовательские атрибуты должны быть производными от System.Attribute или типа, производным от System.Attribute
.
В следующем примере это правило нарушается. Он определяет NumericAttribute
класс, который не является производным от System.Attribute. Ошибка компилятора возникает только при применении атрибута, не совместимого с CLS, а не при определении класса.
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
' ~~~~~~~~~~~~~
Конструктор или свойства атрибута, совместимого с CLS, могут предоставлять только следующие типы:
В следующем примере определяется класс, производный DescriptionAttribute
от Атрибута. Конструктор классов имеет параметр типа Descriptor
, поэтому класс не соответствует CLS. Компилятор C# выдает предупреждение, но успешно компилируется, в то время как компилятор Visual Basic не выдает предупреждение или ошибку.
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
Атрибут CLSCompliantAttribute
Атрибут CLSCompliantAttribute используется для указания того, соответствует ли элемент программы спецификации Common Language. Конструктор CLSCompliantAttribute(Boolean) содержит один обязательный параметр isCompliant, который указывает, соответствует ли элемент программы clS.
Во время компиляции компилятор обнаруживает несоответствующие элементы, которые, как предполагается, соответствуют CLS, и выдает предупреждение. Компилятор не выдает предупреждения о типах или членах, которые явно объявлены несоответствующими.
Разработчики компонентов могут использовать CLSCompliantAttribute
атрибут двумя способами:
Определение частей общедоступного интерфейса, предоставляемых компонентом, совместимым с CLS, и частями, которые не соответствуют CLS. Если атрибут используется для маркировки определенных элементов программы как CLS-совместимых, его использование гарантирует, что эти элементы доступны на всех языках и средствах, предназначенных для .NET.
Чтобы гарантировать, что общедоступный интерфейс библиотеки компонентов предоставляет только программные элементы, совместимые с CLS. Если элементы не соответствуют требованиям CLS, компиляторы обычно выдают предупреждение.
Предупреждение
В некоторых случаях компиляторы языка применяют правила, совместимые с CLS, независимо от того, используется ли CLSCompliantAttribute
атрибут. Например, определение статического члена в интерфейсе нарушает правило CLS. В этом отношении, если вы определяете член static
(в C#) или Shared
(в Visual Basic) в интерфейсе, компиляторы C# и Visual Basic показывают сообщение об ошибке и не могут скомпилировать приложение.
Атрибут CLSCompliantAttribute помечается атрибутом AttributeUsageAttribute , который имеет значение AttributeTargets.All. Это значение позволяет применять атрибут к любому элементу CLSCompliantAttribute программы, включая сборки, модули, типы (классы, структуры, перечисления, интерфейсы и делегаты), элементы типа (конструкторы, методы, свойства, поля и события), параметры, универсальные параметры и возвращаемые значения. Однако на практике атрибут следует применять только к сборкам, типам и элементам типа. В противном случае компиляторы игнорируют атрибут и продолжают создавать предупреждения компилятора при обнаружении несоответствующего параметра, универсального параметра или возвращаемого значения в общедоступном интерфейсе библиотеки.
Значение атрибута CLSCompliantAttribute наследуется содержащимися элементами программы. Например, если сборка помечена как CLS-совместимая, ее типы также являются совместимыми с CLS. Если тип помечен как CLS-совместимый, его вложенные типы и члены также являются CLS-совместимыми.
Вы можете явно переопределить унаследованное соответствие, применив CLSCompliantAttribute атрибут к автономному элементу программы. Например, можно использовать атрибут с CLSCompliantAttribute значением isCompliant
для определения несоответствующего типа в соответствующей сборке, и атрибут с false
значением isCompliant
для определения соответствующего типа в несоответствующей сборке. Вы также можете определить несоответствующие элементы в типе соответствия. Однако несоответствующий тип не может иметь соответствующих членов, поэтому нельзя использовать атрибут со значением isCompliant
для переопределения наследования от несоответствующего типа.
При разработке компонентов всегда следует использовать атрибут CLSCompliantAttribute, чтобы указать, соответствует ли ваша сборка, её типы и члены спецификации CLS.
Чтобы создать компоненты, совместимые с CLS, выполните следующие действия.
Используйте CLSCompliantAttribute, чтобы пометить вашу сборку как совместимую с CLS.
Отметьте все общедоступные типы в сборке, которые не соответствуют стандарту CLS, как несоответствующие.
Помечайте все общедоступные члены в типах, совместимых с CLS, как не соответствующие требованиям.
Предоставьте альтернативу, совместимую с CLS, для членов, не совместимых с CLS.
Если вы успешно помечаете все несоответствующие типы и члены, компилятор не должен выдавать предупреждения о несоответствии. Однако следует указать, какие члены не соответствуют CLS, и перечислить свои альтернативы, совместимые с CLS, в документации по продукту.
В следующем примере используется атрибут CLSCompliantAttribute для определения сборки, совместимой с CLS, и типа CharacterUtilities
, который содержит два члена, не соответствующих CLS. Так как оба элемента помечены CLSCompliant(false)
атрибутом, компилятор не выдает предупреждений. Класс также предоставляет альтернативу, совместимую с CLS, для обоих методов. Обычно мы просто добавляем две перегрузки метода ToUTF16
для предоставления альтернатив, совместимых с CLS. Однако, поскольку методы не могут быть перегружены на основе возвращаемого значения, имена методов, совместимых с CLS, отличаются от имен несоответствуемых методов.
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
Если вы разрабатываете приложение, а не библиотеку (т. е. если вы не представляете типы или члены, которые могут быть использованы другими разработчиками приложений), соответствие CLS элементов программы, используемых приложением, имеет значение лишь в том случае, если язык программирования не поддерживает их. В этом случае компилятор языка создаст ошибку при попытке использовать несоответствующий элемент CLS.
Взаимодействие между языками
Независимость языка имеет несколько возможных значений. Одно из значений подразумевает интеграцию и использование типов, написанных на одном языке, в приложении, созданном на другом языке. Второе значение, которое является основной частью этой статьи, включает объединение кода, написанного на нескольких языках, в одну сборку .NET.
В следующем примере показано взаимодействие между языками путем создания библиотеки классов с именем Utilities.dll, включающей два класса и NumericLib
StringLib
. Класс NumericLib
записывается в C#, и StringLib
класс записывается в Visual Basic. Вот исходный код для StringUtil.vb
, который включает в себя единственного члена, ToTitleCase
, в своем классе StringLib
.
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
Ниже приведен исходный код для NumberUtil.cs, который определяет класс NumericLib
, имеющий двух членов: IsEven
и NearZero
.
using System;
public static class NumericLib
{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return Convert.ToInt64(number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) % 2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}
public static bool NearZero(double number)
{
return Math.Abs(number) < .00001;
}
}
Чтобы упаковать два класса в одной сборке, их необходимо скомпилировать в модули. Чтобы скомпилировать файл исходного кода Visual Basic в модуль, используйте следующую команду:
vbc /t:module StringUtil.vb
Дополнительные сведения о синтаксисе командной строки компилятора Visual Basic см. в статье "Создание из командной строки".
Чтобы скомпилировать файл исходного кода C# в модуль, используйте следующую команду:
csc /t:module NumberUtil.cs
Затем можно использовать параметры компоновщика для компиляции двух модулей в сборку:
link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll
Следующий пример вызывает методы NumericLib.NearZero
и StringLib.ToTitleCase
. Код Visual Basic и код C# могут получить доступ к методам в обоих классах.
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
Чтобы скомпилировать код Visual Basic, используйте следующую команду:
vbc example.vb /r:UtilityLib.dll
Чтобы выполнить компиляцию с помощью C#, измените имя компилятора на vbc
csc
и измените расширение файла с .vb на .cs:
csc example.cs /r:UtilityLib.dll