Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
8.1 Общие
Типы языка C# делятся на две основные категории: ссылочные типы и типы значений. Как типы значений, так и ссылочные типы могут быть универсальными типами, которые принимают один или несколько параметров типа. Параметры типа могут назначать как типы значений, так и ссылочные типы.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) доступна только в небезопасном коде (§23).
Типы значений отличаются от ссылочных типов тем, что переменные типов значений непосредственно содержат свои данные, тогда как переменные ссылочных типов хранят ссылки на свои данные, последние называются объектами. С ссылочными типами можно ссылаться на один и тот же объект и, следовательно, использовать операции с одной переменной, чтобы повлиять на объект, на который ссылается другая переменная. При использовании типов значений каждая переменная имеет собственную копию данных, и операции с одной не могут повлиять на другую.
Примечание. Если переменная является ссылочным или выходным параметром, она не имеет собственного хранилища, но ссылается на хранилище другой переменной. В данном случае, переменная с модификатором ref или out фактически является псевдонимом другой переменной, а не отдельной переменной. конечная заметка
Система типов C#унифицировано таким образом, что значение любого типа можно рассматривать как объект. Каждый тип в C# напрямую или косвенно является производным от object
типа класса и object
является конечным базовым классом всех типов. Значения ссылочных типов обрабатываются как объекты, просто просматривая значения как тип object
. Значения типов значений рассматриваются как объекты через операции упаковки и распаковки (§8.3.13).
Для удобства в этой спецификации некоторые имена типов библиотек записываются без использования их полной квалификации. Дополнительные сведения см. в разделе "C.5 ".
Типы ссылок 8.2
8.2.1 Общие
Ссылочный тип — это тип класса, тип интерфейса, тип массива, тип делегата или dynamic
тип. Для каждого ссылочного типа, не допускающего значение NULL, существует соответствующий ссылочный тип, допускающий значение NULL, отмеченный добавлением ?
к имени типа.
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type доступен только в небезопасном коде (§23.3). nullable_reference_type обсуждается далее в §8.9.
Значение ссылочного типа — это ссылка на экземпляр типа, который называется объектом. Специальное значение null
совместимо с любыми ссылочными типами и обозначает отсутствие экземпляра.
Типы классов 8.2.2
Тип класса определяет структуру данных, содержащую элементы данных (константы и поля), члены функции (методы, свойства, события, индексаторы, операторы, конструкторы экземпляров, методы завершения и статические конструкторы) и вложенные типы. Типы классов поддерживают наследование, механизм, в котором производные классы могут расширять и специализировать базовые классы. Экземпляры типов классов создаются с помощью object_creation_expressions (§12.8.17.2).
Типы классов описаны в разделе §15.
Некоторые предопределенные типы классов имеют особое значение на языке C#, как описано в таблице ниже.
Тип класса | Описание |
---|---|
System.Object |
Основной базовый класс всех других типов. См. статью 8.2.3. |
System.String |
Строковый тип языка C#. См. статью 8.2.5. |
System.ValueType |
Базовый класс всех типов значений. См. статью 8.3.2. |
System.Enum |
Базовый класс всех enum типов. См. статью 19.5. |
System.Array |
Базовый класс всех типов массивов. См. статью 17.2.2. |
System.Delegate |
Базовый класс всех delegate типов. См. статью 20.1. |
System.Exception |
Базовый класс всех типов исключений. См. статью 21.3. |
8.2.3 Тип объекта
Тип object
класса является конечным базовым классом всех других типов. Каждый тип в C# напрямую или косвенно является производным от object
типа класса.
Ключевое слово object
— это просто псевдоним для предопределенного класса System.Object
.
8.2.4 Динамический тип
Тип dynamic
, например object
, может ссылаться на любой объект. Если операции применяются к выражениям типа dynamic
, их разрешение откладывается до запуска программы. Таким образом, если операция не может быть применена к указанному объекту, во время компиляции ошибка не будет указана. Вместо этого будет выброшено исключение в случае ошибки разрешения операции во время выполнения программы.
Тип dynamic
описан далее в §8.7 и динамической привязке в §12.3.1.
8.2.5 Тип строки
Тип string
является запечатанным типом класса, который наследуется непосредственно от object
. Экземпляры string
класса представляют строки символов Юникода.
string
Значения типа можно записать как строковые литералы (§6.4.5.6).
Ключевое слово string
— это просто псевдоним для предопределенного класса System.String
.
Типы интерфейсов 8.2.6
Интерфейс определяет контракт. Класс или структура, реализующая интерфейс, должна соответствовать своему контракту. Интерфейс может наследовать от нескольких базовых интерфейсов, а класс или структура может реализовать несколько интерфейсов.
Типы интерфейсов описаны в разделе §18.
Типы массивов 8.2.7
Массив представляет собой структуру данных, содержащую ноль или более переменных, к которым осуществляется доступ с помощью вычисляемых индексов. Переменные, содержащиеся в массиве, также называемые элементами массива, имеют одинаковый тип, и этот тип называется типом элемента массива.
Типы массивов описаны в разделе §17.
Типы делегатов 8.2.8
Делегат — это объект данных, который ссылается на один или несколько методов. Например, методы также ссылаются на соответствующие экземпляры объектов.
Примечание. Ближайший эквивалент делегата в C или C++ является указателем функции, но в то время как указатель функции может ссылаться только на статические функции, делегат может ссылаться как на статические, так и методы экземпляра. В последнем случае делегат сохраняет не только ссылку на точку входа метода, но и ссылку на экземпляр объекта, в котором вызывается метод. конечная заметка
Типы делегатов описаны в разделе §20.
Типы значений 8.3
8.3.1 Общие положения
Тип значения — это тип структуры или тип перечисления. C# предоставляет набор предопределенных типов структур, называемых простыми типами. Простые типы определяются с помощью ключевых слов.
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
В отличие от переменной ссылочного типа, переменная типа значения может содержать значение null
только в том случае, если тип значения имеет значение NULL (§8.3.12). Для каждого типа значения, не допускающего значение NULL, существует соответствующий тип значения NULL, обозначающий один набор значений плюс значение null
.
Назначение переменной типа значения создает копию присваиваемого значения. Это отличается от назначения переменной ссылочного типа, которая копирует ссылку, но не объект, определенный ссылкой.
8.3.2 Тип System.ValueType
Все типы значений неявно наследуются от class
System.ValueType
, который, в свою очередь, наследуется от класса object
. Для любого типа невозможно наследовать от типа значения, а типы значений таким образом неявно запечатаны (§15.2.2.3).
Обратите внимание, что System.ValueType
сам не является value_type. Скорее, это class_type, от которого все value_types автоматически выведены.
Конструкторы по умолчанию 8.3.3
Все типы значений неявно объявляют открытый конструктор экземпляра без параметров, называемый конструктором по умолчанию. Конструктор по умолчанию возвращает нулевой инициализированный экземпляр, известный как значение по умолчанию для типа значения:
- Для всех simple_type значение по умолчанию — это значение, созданное битовым шаблоном всех нулей:
- Для
sbyte
,byte
,short
,ushort
,int
,uint
,long
иulong
значение по умолчанию —0
. - Для
char
параметра по умолчанию используется'\x0000'
значение . - Для
float
параметра по умолчанию используется0.0f
значение . - Для
double
параметра по умолчанию используется0.0d
значение . - Для
decimal
значение по умолчанию составляет0m
(то есть нулевое значение с масштабом 0). - Для
bool
параметра по умолчанию используетсяfalse
значение . - Для enum_type
E
, значение по умолчанию0
равно преобразованному в типE
.
- Для
- Для struct_type значение по умолчанию — это значение, созданное путем установки всех полей типа значения в значение по умолчанию и всех полей ссылочного типа в значение
null
. - Для nullable_value_type значение по умолчанию — это экземпляр, для которого свойство имеет значение false. Значение по умолчанию также называется значением NULL типа значения, допускающего значение NULL. Попытка чтения свойства
Value
такого значения приводит к возникновению исключения типаSystem.InvalidOperationException
, §8.3.12.
Как и любой другой конструктор экземпляра, конструктор по умолчанию типа значения вызывается с помощью new
оператора.
Примечание. По соображениям эффективности это требование не предназначено для фактического создания вызова конструктора. Для типов значений выражение по умолчанию (§12.8.21) создает тот же результат, что и конструктор по умолчанию. конечная заметка
Пример: В приведенном ниже коде переменные
i
,j
иk
все инициализированы в ноль.class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
заключительный пример
Поскольку каждый тип значения неявно имеет открытый конструктор экземпляра без параметров, тип структуры не может содержать явное объявление конструктора без параметров. Однако тип структуры может объявлять параметризованные конструкторы экземпляров (§16.4.9).
Типы структур 8.3.4
Тип структуры — это тип значения, который может объявлять константы, поля, методы, свойства, события, индексаторы, операторы, конструкторы экземпляров, статические конструкторы и вложенные типы. Объявление типов структур описано в разделе §16.
8.3.5 Простые типы
C# предоставляет набор стандартных struct
типов, называемых простыми типами. Простые типы определяются с помощью ключевых слов, но эти ключевые слова являются просто псевдонимами для предопределенных типов struct
в пространстве имен System
, как описано в таблице ниже.
Ключевое слово | Псевдоним типа |
---|---|
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
Поскольку простой псевдоним типа является структурным типом, каждый простой тип имеет члены.
Пример:
int
имеются члены, объявленные вSystem.Int32
, и члены, унаследованные отSystem.Object
, и разрешены следующие операторы:int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method
заключительный пример
Примечание. Простые типы отличаются от других типов структур в том, что они разрешают определенные дополнительные операции:
- Большинство простых типов позволяют создавать значения путем написания литералов (§6.4.5), хотя C# вообще не подготавливает литералы типов структур. Пример:
123
— это литерал типаint
, а'a'
— литерал типаchar
. заключительный пример- Если операнды выражения являются всеми простыми константами типов, компилятор может оценить выражение во время компиляции. Такое выражение называется constant_expression (§12.23). Выражения, связанные с операторами, определенными другими типами структур, не считаются константными выражениями
- С помощью
const
объявлений можно объявить константы простых типов (§15.4). Невозможно иметь константы других типов структур, но аналогичный эффект обеспечивается статическими полями чтения.- Преобразования, включающие простые типы, могут участвовать в оценке операторов преобразования, определенных другими типами структур, но определяемый пользователем оператор преобразования никогда не может участвовать в оценке другого определяемого пользователем оператора преобразования (§10.5.3).
конечная заметка.
8.3.6 Целочисленные типы
C# поддерживает девять целочисленных типов: sbyte
, byte
short
ushort
int
uint
, , long
ulong
и .char
Целочисленные типы имеют следующие размеры и диапазоны значений:
- Тип
sbyte
представляет 8-разрядные целые числа со значениями от-128
до127
,включительно. - Тип
byte
представляет 8-разрядные целые числа без знака со значениями от0
до255
,включительно. - Тип
short
представляет 16-разрядные целые числа со значениями от-32768
до32767
,включительно. - Тип
ushort
представляет 16-разрядные целые числа без знака со значениями от0
до65535
,включительно. - Тип
int
представляет 32-разрядные целые числа со значениями от-2147483648
до2147483647
,включительно. - Тип
uint
представляет 32-разрядные целые числа без знака со значениями от0
до4294967295
,включительно. - Тип
long
представляет 64-разрядные целые числа со значениями от-9223372036854775808
до9223372036854775807
,включительно. - Тип
ulong
представляет 64-разрядные целые числа без знака со значениями от0
до18446744073709551615
,включительно. - Тип
char
представляет 16-разрядные целые числа без знака со значениями от0
до65535
,включительно. Набор возможных значений дляchar
типа соответствует набору символов Юникода.Примечание. Хотя
char
имеет то же представление, что иushort
, не все операции, разрешенные для одного типа, разрешены для другого. конечная заметка
Все подписанные целочисленные типы представлены с помощью двух форматов дополнения.
Integral_type унарные и двоичные операторы всегда работают с точностью до подписанных 32 бит, до беззнаковых 32 бит, до подписанных 64 бит или до беззнаковых 64 бит, как описано в разделе 12.4.7.
Тип char
классифицируется как целочисленный тип, но отличается от других целочисленных типов двумя способами:
- Предопределенные неявные преобразования из других типов в
char
тип отсутствуют. В частности, хотя типыbyte
иushort
имеют диапазоны значений, которые полностью представляются с помощью типаchar
, не существует неявных преобразований из sbyte, byte илиushort
вchar
. - Константы типа
char
должны быть записаны как character_literal или как integer_literal в сочетании с приведением к типу char.
Пример:
(char)10
совпадает с'\x000A'
. заключительный пример
Операторы checked
и unchecked
используются для управления проверкой переполнения для арифметических операций и преобразований целочисленного типа (§12.8.20). В контексте checked
переполнение приводит к появлению ошибки компиляции или вызывается исключение System.OverflowException
. В контексте unchecked
переполнения игнорируются, а все биты высокого порядка, которые не соответствуют типу назначения, удаляются.
8.3.7 Типы с плавающей запятой
C# поддерживает два типа с плавающей запятой: float
и double
.
float
и double
типы представлены в формате 32-разрядной одиночной точности и 64-разрядной двойной точности по стандартам IEC 60559, которые предоставляют следующие наборы значений:
- Положительный нуль и отрицательный ноль. В большинстве случаев положительный ноль и отрицательный нуль ведут себя одинаково, как простое нулевое значение, но некоторые операции различаются между двумя (§12.10.3).
- Положительная бесконечность и отрицательная бесконечность. Бесконечности возникают в результате таких операций, как деление ненулевого числа на ноль.
Пример:
1.0 / 0.0
дает положительную бесконечность и–1.0 / 0.0
дает отрицательную бесконечность. заключительный пример - Значение Not-a-Number, часто сокращаемое как NaN. NaN создаются недопустимыми операциями с плавающей запятой, например делением нуля на ноль.
- Конечный набор ненулевых значений формы s × м × 2e, где s равно 1 или –1, а м и e определяются определенным типом с плавающей запятой: для
float
, 0 <м< 2²⁴ и –149 ≤ e ≤ 104, а дляdouble
, 0 <м< 2⁵³ и –1075 ≤ e ≤ 970. Денормализованные числа с плавающей запятой считаются допустимыми ненулевыми значениями. C# не требует и не запрещает, что соответствующая реализация поддерживает денормализованные числа с плавающей запятой.
Тип float
может представлять значения от приблизительно 1,5 × 10⁻⁴⁵ до 3,4 × 10 ⁸ с точностью 7 цифр.
Тип double
может представлять значения от приблизительно 5,0 × 10⁻⁴ до 1,7 × 10⁸ с точностью 15-16 цифр.
Если любой из операндов двоичного оператора является типом с плавающей точкой, то применяются стандартные числовые повышения, как описано в разделе 12.4.7, и операция выполняется с float
или double
точностью.
Операторы с плавающей запятой, включая операторы присваивания, никак не генерируют исключения. Вместо этого в исключительных ситуациях операции с плавающей запятой создают ноль, бесконечность или NaN, как описано ниже:
- Результат операции с плавающей запятой округляется до ближайшего представляющего значения в целевом формате.
- Если величина результата операции с плавающей запятой слишком мала для целевого формата, результат операции становится положительным нулем или отрицательным нулем.
- Если величина результата операции с плавающей запятой слишком велика для целевого формата, результат операции становится положительной бесконечностью или отрицательной бесконечностью.
- Если операция с плавающей запятой недопустима, результат операции становится naN.
- Если хотя бы один операнд операции с плавающей запятой — NaN, то результат этой операции также будет NaN.
Операции с плавающей запятой могут выполняться с более высокой точностью, чем тип результата операции. Чтобы принудительно привести значение типа с плавающей запятой к точности, соответствующей его типу, можно использовать явное приведение (§12.9.7).
Пример. Некоторые аппаратные архитектуры поддерживают тип с плавающей запятой "extended" или "long double" с большей точностью и диапазоном, чем
double
тип, и неявно выполняют все операции с плавающей запятой, используя этот тип более высокой точности. Только при чрезмерных затратах на производительность такие аппаратные архитектуры могут выполнять операции с плавающей запятой с меньшей точностью. Вместо того чтобы требовать от реализации жертвовать и производительностью, и точностью, C# позволяет использовать более высокий тип точности для всех операций с плавающей запятой. Кроме более точных результатов, это редко имеет измеримые эффекты. Однако в выражениях формыx * y / z
, где умножение создает результат, который находится за пределамиdouble
диапазона, но последующее деление возвращает временный результатdouble
обратно в диапазон, тот факт, что выражение вычисляется в более высоком формате диапазона, может привести к тому, что конечный результат будет производиться вместо бесконечности. заключительный пример
8.3.8 Десятичный тип
Тип decimal
— это 128-разрядный тип данных, подходящий для финансовых и денежных вычислений. Тип decimal
может представлять значения, включая значения в диапазоне по крайней мере –7,9 × 10⁻⁸ до 7,9 × 10 ⁸ с по крайней мере 28-разрядной точностью.
Конечный набор значений типа decimal
имеет форму (–1)ᵛ × c × 10⁻ᵉ, где знак v равен 0 или 1, коэффициент c определяется как 0 ≤ c<Cmax, а масштаб e такой, что Emin ≤ e ≤ Emax, где Cmax составляет не менее 1 × 10²⁸, Emin ≤ 0, и Emax ≥ 28. Тип decimal
необязательно поддерживает знаковые нули, бесконечности или NaN.
А decimal
представляется целым числом, масштабируемым по 10. Для decimal
s с абсолютным значением меньше 1.0m
, значение точно равно 28-му десятичному разряду. Для decimal
, абсолютное значение которых больше или равно 1.0m
, значение точно до не менее чем 28 цифр. В отличие от типов данных float
и double
, десятичные дробные числа, такие как 0.1
, можно представить точно в десятичном представлении. В представлениях float
и double
такие числа часто имеют бесконечные двоичные расширения, из-за чего эти представления более склонны к ошибкам округления.
Если любой операнд двоичного оператора имеет decimal
тип, то применяются стандартные числовые повышения, как описано в разделе 12.4.7, и операция выполняется с double
точностью.
Результат операции со значениями типа decimal
получается путем вычисления точного результата (с сохранением масштаба, определенного для каждого оператора), а затем округления для соответствия представлению. Результаты округляются до ближайшего представляющего значения, и, когда результат равно близок к двум представленным значениям, к значению, которое имеет четное число в наименьшей значительной позиции цифры (это называется округлением банкира). То есть результаты точны по крайней мере до 28-го десятичного разряда. Обратите внимание, что округление может привести к нулю из ненулевого значения.
Если арифметическая операция decimal
возвращает значение, которое слишком велико для decimal
формата, создается System.OverflowException
исключение.
Тип decimal
имеет большую точность, но может иметь меньший диапазон, чем типы с плавающей запятой. Таким образом, преобразования из типов с плавающей запятой в decimal
могут вызвать исключения переполнения, а преобразования из decimal
в типы с плавающей запятой могут привести к потере точности или исключениям переполнения. По этим причинам не существует неявных преобразований между типами с плавающей запятой и decimal
, и без явных приведения происходит ошибка компиляции, когда операнды с плавающей запятой и decimal
непосредственно объединяются в одном выражении.
8.3.9 Тип Bool
Тип bool
представляет булевые логические величины. Возможные значения типа bool
это true
и false
. Представление false
описано в §8.3.3 разделе. Хотя представление true
не указано, оно должно отличаться от false
представления.
Стандартные преобразования между bool
и другими типами значений не существуют. В частности, bool
тип отличается и отделен от целочисленных типов, bool
значение не может использоваться вместо целочисленного значения, и наоборот.
Примечание. На языках C и C++ нулевое целочисленное или плавающее значение или указатель null можно преобразовать в логическое
false
значение, а ненулевой целочисленный или с плавающей запятой, или ненулевой указатель можно преобразовать в логическое значениеtrue
. В C#такие преобразования выполняются явным образом путем сравнения целочисленного или плавающего значения с нулем или путем явного сравнения ссылки наnull
объект. конечная заметка
Типы перечисления 8.3.10
Тип перечисления — это отдельный тип с именованными константами. Каждый тип перечисления имеет базовый тип, который должен быть byte
, sbyte
, short
, ushort
, int
uint
long
или ulong
. Набор значений типа перечисления совпадает с набором значений базового типа. Значения типа перечисления не ограничиваются значениями именованных констант. Типы перечисления определяются через объявления перечисления (§19.2).
Типы кортежей 8.3.11
Тип кортежа представляет упорядоченную последовательность значений фиксированной длины, которая может содержать необязательные имена и уникальные типы для каждого элемента. Число элементов в типе кортежа называется его арность. Тип кортежа записывается (T1 I1, ..., Tn In)
с n ≥ 2, где идентификаторы I1...In
являются необязательными именами элементов кортежа.
Этот синтаксис является сокращением для типа, созданного с помощью типов T1...Tn
из System.ValueTuple<...>
, которые должны быть набором универсальных типов структур, способных напрямую выразить типы кортежей любой арности в диапазоне от двух до семи включительно.
Не требуется наличие объявления System.ValueTuple<...>
, которое напрямую соответствует арности любого типа кортежа с соответствующим количеством параметров типа. Вместо этого кортежи с arity больше семи представлены универсальным типом System.ValueTuple<T1, ..., T7, TRest>
структуры, который помимо элементов кортежа имеет Rest
поле, содержащее вложенное значение остальных элементов, используя другой System.ValueTuple<...>
тип. Такое вложение может наблюдаться различными способами, например через наличие поля Rest
. Если требуется только одно дополнительное поле, используется универсальный тип System.ValueTuple<T1>
структуры. Этот тип не считается типом кортежа. Если требуется более семи дополнительных полей, System.ValueTuple<T1, ..., T7, TRest>
используется рекурсивно.
Имена элементов в типе кортежа должны отличаться. Имя элемента кортежа формы ItemX
, где X
— это любая последовательность десятичных цифр, не инициированных 0
, которая может представлять позицию элемента кортежа, допускается только в позиции, обозначенной X
.
Необязательные имена элементов не представлены в типах и не хранятся в ValueTuple<...>
представлении значения кортежа во время выполнения. Преобразования идентичности (§10.2.2) существуют между кортежами с последовательностями типов элементов, которые могут быть преобразованы по идентичности.
Оператору new
§12.8.17.2 нельзя применять синтаксис типа кортежа new (T1, ..., Tn)
. Значения кортежей можно создавать из выражений кортежа (§12.8.6) или применением оператора new
непосредственно к типу, созданному из ValueTuple<...>
.
Элементы кортежа — это общедоступные поля с именами Item1
, Item2
и т. д., к которым можно получить доступ через доступ к элементу по значению кортежа (§12.8.7. Кроме того, если тип кортежа имеет имя для заданного элемента, это имя можно использовать для доступа к рассматриваемому элементу.
Примечание. Даже если большие кортежи представлены в виде вложенных
System.ValueTuple<...>
значений, каждый элемент кортежа все равно можно получить напрямую, используя имяItem...
, соответствующее его позиции. конечная заметка
Пример: Даны следующие примеры:
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");
Типы кортежей для
pair1
,pair2
, иpair3
все допустимы, с именами для некоторых или всех элементов кортежа, или без них.Тип
pair4
кортежа является допустимым, так как именаItem1
иItem2
соответствуют их позициям, в то время как тип кортежа дляpair5
запрещен, потому что именаItem2
иItem123
не соответствуют.Объявления для
pair6
иpair7
демонстрируют, что типы кортежей взаимозаменяемы с созданными типами формыValueTuple<...>
, и чтоnew
оператор разрешен с последним синтаксисом.Последняя строка показывает, что элементы кортежа можно получить по
Item
имени, соответствующему их положению, а также по соответствующему имени элемента кортежа, если они присутствуют в типе. заключительный пример
8.3.12 Типы значений, допускающих значение NULL
Тип значения, допускающего значение NULL, может представлять все значения базового типа, а также дополнительное значение NULL. Тип значения, допускающий NULL, записывается как T?
, где T
— это базовый тип. Этот синтаксис - это краткая запись для System.Nullable<T>
, и эти две формы можно использовать взаимозаменяемо.
И наоборот, не допускающий значения NULL тип значения является любым типом значения, отличным от System.Nullable<T>
и его сокращенной версии T?
(для любого T
), а также любой параметр типа, который ограничен как тип значения, не допускающий значения NULL (то есть любой параметр типа с ограничением типа значения (§15.2.5)). Тип System.Nullable<T>
задает ограничение типа значения для T
, что означает, что базовый тип типа значения, допускающего значение NULL, может быть любым типом значения, не допускающим значение NULL. Базовый тип типа значения, допускающего значение NULL, не может быть типом значения null или ссылочным типом. Например, int??
недопустимый тип. Ссылочные типы, допускающие значение NULL, рассматриваются в разделе §8.9.
Экземпляр типа T?
значения, допускающего значение NULL, имеет два общедоступных свойства только для чтения:
-
HasValue
Свойство типаbool
-
Value
Свойство типаT
Экземпляр, для которого HasValue
true
, считается не являющимся NULL. Экземпляр, отличный от NULL, содержит известное значение и Value
возвращает это значение.
Экземпляр, для которого HasValue
является false
, считается нулевым. Экземпляр NULL имеет неопределенное значение. Попытка прочитать Value
недействительного объекта вызывает System.InvalidOperationException
. Процесс извлечения свойству Value из объекта со значением NULL называется распаковкой.
В дополнение к конструктору по умолчанию каждый тип значения T?
, допускающий значение nullable, имеет открытый конструктор с одним параметром типа T
. Если задано значение x
типа T
, вызов конструктора формы
new T?(x)
создает ненулевой экземпляр T?
, для которого свойство Value
равно x
. Процесс создания экземпляра значения, отличного от NULL, для заданного значения называется оболочкой.
Неявные преобразования доступны из литерала null
в T?
(§10.2.7) и из T
в T?
(§10.2.6).
NULL-допускаемый тип T?
не реализует никаких интерфейсов (§18). В частности, это означает, что он не реализует интерфейс, который выполняет базовый тип T
.
8.3.13 Бокс и распаковка
Процесс бокса и распаковки обеспечивает мост между value_type и reference_type, разрешая преобразование любого значения value_type в тип и из типа object
. Упаковка и распаковка позволяют представить систему типов как унифицированную, в которой значение любого типа в конечном итоге можно рассматривать как object
.
Бокс описан более подробно в разделе §10.2.9 и распаковка описана в разделе §10.3.7.
8.4 Созданные типы
8.4.1 Общие
Объявление универсального типа, само по себе означает несвязанный универсальный тип , который используется в качестве схемы для формирования множества различных типов путем применения аргументов типа. Аргументы типа записываются в угловых скобках (<
и >
) сразу после имени универсального типа. Тип, включающий по крайней мере один аргумент типа, называется созданным типом. Созданный тип можно использовать в большинстве случаев в языке, где может встречаться имя типа. Несвязанный универсальный тип можно использовать только в typeof_expression (§12.8.18).
Созданные типы также можно использовать в выражениях как простые имена (§12.8.4) или при доступе к члену (§12.8.7).
При оценке namespace_or_type_name учитываются только универсальные типы с правильным числом параметров типа. Таким образом, можно использовать один и тот же идентификатор для идентификации различных типов, если типы имеют разные числа параметров типа. Это полезно при сочетании универсальных и не универсальных классов в одной программе.
Пример:
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }
заключительный пример
Подробные правила поиска имен в рабочей среде namespace_or_type_name описаны в разделе 7.8. Разрешение неоднозначностей в этих производных описано в разделе 6.2.5.
Type_name может определить созданный тип, даже если он не задает параметры типа напрямую. Это может произойти, когда тип вложен в универсальное class
объявление, а тип экземпляра содержащего объявления неявно используется для поиска имен (§15.3.9.7).
Пример:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
заключительный пример
Тип, не являющийся перечислением, не должен использоваться в качестве unmanaged_type (§8.8).
Аргументы типа 8.4.2
Каждый аргумент в списке аргументов типа — это просто тип.
type_argument_list
: '<' type_argument (',' type_argument)* '>'
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
Каждый аргумент типа должен соответствовать любым ограничениям соответствующего параметра типа (§15.2.5). Аргумент ссылочного типа, значение NULL которого не соответствует значению NULL параметра типа, удовлетворяет ограничению; однако может быть выдано предупреждение.
8.4.3 Открытые и закрытые типы
Все типы можно классифицировать как открытые типы или закрытые типы. Открытый тип — это тип, который включает параметры типа. В частности:
- Параметр типа определяет открытый тип.
- Тип массива является открытым типом, если и только если его тип элемента является открытым типом.
- Созданный тип является открытым типом, если и только если один или несколько его аргументов типа являются открытым типом. Созданный вложенный тип является открытым типом, если один или несколько его аргументов типа или аргументы типа одного или нескольких его содержащих типов являются открытым типом.
Закрытый тип — это тип, который не является открытым типом.
Во время выполнения весь код в объявлении универсального типа выполняется в контексте закрытого созданного типа, созданного путем применения аргументов типа к универсальному объявлению. Каждый параметр типа в универсальном типе привязан к определенному типу времени выполнения. Обработка всех инструкций и выражений во время выполнения всегда происходит с закрытыми типами, а открытые типы происходят только во время компиляции.
Два закрытых конструктивных типа тождественно преобразуемы (§10.2.2), если они созданы из одного и того же несвязанного универсального типа, и тождественное преобразование существует между каждым из их соответствующих аргументов типа. Соответствующие аргументы типа могут быть закрытыми конструкциями типов или кортежами, которые могут быть преобразованы с сохранением идентичности. Закрытые конструированные типы, которые можно преобразовать посредством идентификации, совместно используют один набор статических переменных. В противном случае каждый закрытый созданный тип имеет собственный набор статических переменных. Так как открытый тип не существует во время выполнения, статические переменные, связанные с открытым типом, отсутствуют.
8.4.4 Привязанные и несвязанные типы
Термин unbound type ссылается на неуниверсальный тип или непривязанный универсальный тип. Термин связанный тип означает необобщенный тип или построенный тип.
Несвязанный тип относится к сущности, объявленной объявлением типа. Несвязанный универсальный тип не является типом и не может использоваться в качестве типа переменной, аргумента или возвращаемого значения или в качестве базового типа. Единственная конструкция, в которой можно ссылаться на несвязанный универсальный тип, — typeof
выражение (§12.8.18).
Удовлетворение ограничений 8.4.5.
Всякий раз, когда ссылается созданный тип или универсальный метод, указанные аргументы типа проверяются на ограничения параметров типа, объявленные для универсального типа или метода (§15.2.5). Для каждого where
предложения аргумент A
типа, соответствующий параметру именованного типа, проверяется по каждому ограничению следующим образом:
- Если ограничение является типом
class
, интерфейсным типом или параметром типа, пустьC
представляет это ограничение с аргументами указанных типов, заменяющими любые параметры типов, присутствующие в ограничении. Для удовлетворения ограничения должно быть выполнено следующее: типA
преобразуем в типC
одним из следующих способов: - Если ограничение является ограничением ссылочного типа (
class
), типA
должен соответствовать одному из следующих:-
A
— это тип интерфейса, тип класса, тип делегата, тип массива или динамический тип.
Примечание.
System.ValueType
ИSystem.Enum
являются ссылочными типами, удовлетворяющими этому ограничению. конечная заметка-
A
— это параметр типа, который, как известно, является ссылочным типом (§8.2).
-
- Если ограничение является ограничением типа значения (
struct
), типA
должен соответствовать одному из следующих:-
A
struct
— это тип илиenum
тип, но не тип значения, допускающий значение NULL.
Примечание.
System.ValueType
ИSystem.Enum
являются ссылочными типами, которые не удовлетворяют этому ограничению. конечная заметка-
A
— это параметр типа с ограничением типа значения (§15.2.5).
-
- Если ограничение является ограничением конструктора
new()
, типA
не должен бытьabstract
и должен иметь открытый конструктор без параметров. Это условие выполнено, если выполняется одно из следующих:-
A
— это тип значения, так как все типы значений имеют открытый конструктор по умолчанию (§8.3.3). -
A
— параметр типа с ограничением конструктора (§15.2.5). -
A
— это параметр типа с ограничением типа значения (§15.2.5). -
A
— это не абстрактныйclass
и содержащий явно объявленный открытый конструктор без параметров. -
A
не являетсяabstract
и имеет конструктор по умолчанию (§15.11.5).
-
Ошибка во время компиляции возникает, если одно или несколько ограничений параметра типа не удовлетворяют заданным аргументам типа.
Так как параметры типа не наследуются, ограничения никогда не наследуются.
Пример. В следующем случае необходимо указать ограничение для параметра типа
D
,T
чтобыT
удовлетворить ограничение, введенное базойclass
B<T>
. В отличие от этого,class
E
не нуждаются в указании ограничения, так какList<T>
реализуетIEnumerable
для любогоT
.class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
заключительный пример
8.5 Параметры типа
Параметр типа — это идентификатор, указывающий тип значения или ссылочный тип, к которому привязан параметр во время выполнения.
type_parameter
: identifier
;
Так как параметр типа можно создать экземпляр с множеством разных аргументов типа, параметры типа имеют несколько разные операции и ограничения, отличные от других типов.
Примечание. К ним относятся:
- Параметр типа нельзя использовать непосредственно для объявления базового класса (§15.2.4.2) или интерфейса (§18.2.4).
- Правила поиска элементов для параметров типа зависят от ограничений, применяемых к параметру типа. Они подробно описаны в §12.5.
- Доступные преобразования для параметра типа зависят от ограничений, применяемых к параметру типа. Они подробно описаны в §10.2.12 и §10.3.8.
- Литерал
null
нельзя преобразовать в тип, заданный параметром типа, за исключением того, что параметр типа известен как ссылочный тип (§10.2.12). Однако вместо этого можно использовать выражение по умолчанию (§12.8.21). Кроме того, значение с типом, заданным параметром типа, можно сравнить с null с помощью==
и!=
(§12.12.7), если параметр типа не имеет ограничения типа значения.new
Выражение (§12.8.17.2) можно использовать только с параметром типа, если параметр типа ограничен constructor_constraint или ограничением типа значения (§15.2.5).- Параметр типа нельзя использовать в любом месте атрибута.
- Параметр типа нельзя использовать в доступе к члену (§12.8.7) или имени типа (§7.8) для идентификации статического элемента или вложенного типа.
- Параметр типа нельзя использовать в качестве unmanaged_type (§8.8).
конечная заметка
В качестве типа параметры типа являются исключительно конструкцией во время компиляции. Во время выполнения каждый параметр типа привязан к типу времени выполнения, указанному путем предоставления аргумента типа объявлению универсального типа. Таким образом, тип переменной, объявленной с параметром типа, во время выполнения будет закрытым созданным типом §8.4.3. Выполнение всех инструкций и выражений с параметрами типа во время выполнения использует тип, предоставленный в качестве аргумента типа для этого параметра.
Типы дерева выражений 8.6
Деревья выражений позволяют лямбда-выражениям представляться как структуры данных вместо исполняемого кода. Деревья выражений — это значения типов дерева выражений формы System.Linq.Expressions.Expression<TDelegate>
, где TDelegate
имеется любой тип делегата. Для оставшейся части этой спецификации эти типы будут обозначаться сокращением Expression<TDelegate>
.
Если преобразование существует из лямбда-выражения в тип D
делегата, преобразование также существует в тип Expression<TDelegate>
дерева выражений. В то время как преобразование лямбда-выражения в тип делегата создает делегат, который ссылается на исполняемый код для лямбда-выражения, преобразование в тип дерева выражений создает представление дерева выражений лямбда-выражения. Дополнительные сведения об этом преобразовании приведены в разделе §10.7.3.
Пример. Следующая программа представляет лямбда-выражение как исполняемый код, так и в виде дерева выражений. Поскольку преобразование существует в
Func<int,int>
, преобразование также существует вExpression<Func<int,int>>
:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
После этих назначений делегат
del
ссылается на метод, возвращающийx + 1
, и дерево выражений ссылается на структуру данных, описывающую выражениеx => x + 1
.заключительный пример
Expression<TDelegate>
предоставляет экземплярный метод Compile
, который создаёт делегат типа TDelegate
:
Func<int,int> del2 = exp.Compile();
Вызов этого делегата приводит к выполнению кода, представленного деревом выражений. Таким образом, учитывая приведенные выше определения, del
и del2
эквивалентны, и следующие два оператора будут иметь одинаковый эффект.
int i1 = del(1);
int i2 = del2(1);
После выполнения этого кода i1
и i2
будет иметь значение 2
.
Область API, предоставляемая реализацией Expression<TDelegate>
, определяется за пределами требований к методу Compile
, описанному выше.
Примечание: Хотя детали API, предоставляемого для деревьев выражений, определяются реализацией, ожидается, что реализация:
- Включение кода для проверки и реагирования на структуру дерева выражений, созданного в результате преобразования из лямбда-выражения
- Включение создания деревьев выражений программным образом в пользовательском коде
конечная заметка
8.7 Динамический тип
Тип dynamic
использует динамическую привязку, как описано подробно в §12.3.2, в отличие от статической привязки, используемой всеми другими типами.
Тип dynamic
считается идентичным object
, за исключением следующих аспектов:
- Операции с выражениями типа
dynamic
могут быть динамически привязаны (§12.3.3). - Вывод типов (§12.6.3) предпочтет
dynamic
вместоobject
, если оба являются кандидатами. -
dynamic
нельзя использовать как- Тип в выражении создания объекта (object_creation_expression) (§12.8.17.2)
- class_base (§15.2.4)
- predefined_type в member_access (§12.8.7.1)
- операнд
typeof
оператора - аргумент атрибута
- ограничение
- Тип метода расширения
- любая часть аргумента типа в struct_interfaces (§16.2.5) или interface_type_list (§15.2.4.1).
В связи с этой эквивалентностью, следующее является верным:
- Существует неявное преобразование идентичности
- между
object
иdynamic
- между созданными типами, которые совпадают при замене
dynamic
наobject
- между типами кортежей, которые совпадают при замене
dynamic
наobject
- между
- Неявные и явные преобразования в и из
object
применяются как кdynamic
, так и изdynamic
. - Подписи, которые совпадают при замене сигнатурой
dynamic
object
, считаются одной и той же сигнатурой. - Тип
dynamic
неотличим от типаobject
во время выполнения. - Выражение типа
dynamic
называется динамическим выражением.
8.8 Неуправляемые типы
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
unmanaged_type — это любой тип, который не является ни reference_type, ни type_parameter, который не ограничен неуправляемым, и не содержит полей экземпляра, тип которых не является unmanaged_type. Другими словами, unmanaged_type является одним из следующих:
-
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,float
,double
,decimal
илиbool
. - Любая enum_type.
- Любой определяемый пользователем struct_type, содержащий поля экземпляров только unmanaged_type.
- Любой параметр типа, который ограничен условием быть неуправляемым.
- Любой pointer_type (§23.3).
8.9 Ссылочные типы и возможность null
8.9.1 Общие
Ссылочный тип, допускающий значение NULL , обозначается добавлением nullable_type_annotation (?
) к ненулевому типу ссылки. Нет семантической разницы между ссылочным типом, не допускаемым значением NULL, и соответствующим типом, допускаемым значением NULL, оба могут быть ссылкой на объект или null
. Наличие или отсутствие nullable_type_annotation объявляет, предназначено ли выражение для разрешения значений NULL или нет. Компилятор может предоставить диагностику, если выражение не используется в соответствии с этим намерением. Значение NULL выражения определяется в файле §8.9.5. Идентификационное преобразование существует между nullable ссылочным типом и соответствующим ненулевым ссылочным типом (§10.2.2).
Существует две формы null для ссылочных типов:
-
Nullable: Тип с допустимым значением null можно назначить
null
. Его состояние по умолчанию — возможно-NULL. -
не допускает null: не допускающей null ссылке не следует присваивать
null
значение. Его состояние по умолчанию — не NULL.
Заметка: Типы
R
иR?
представлены тем же базовым типомR
. Переменная этого базового типа может содержать ссылку на объект или значениеnull
, указывающее "нет ссылки". конечная заметка
Синтаксическое различие между ссылочным типом, допускающим значение null и соответствующим ссылочным типом, не допускающим null позволяет компилятору создавать диагностические сообщения. Компилятор должен разрешать nullable_type_annotation, как это определено в §8.2.1. Диагностика должна быть ограничена предупреждениями. Ни наличие или отсутствие заметок, допускающих значение NULL, ни состояние контекста, допускающего значение NULL, не может изменять время компиляции или поведение среды выполнения программы, за исключением изменений в любых диагностических сообщениях, созданных во время компиляции.
8.9.2 Типы ссылок, не допускающие значение NULL
Ссылочный тип, не допускающий значения null, является ссылочным типом формы T
, где T
является именем типа. Значение null-state для переменной, не допускающей значения NULL, равно не-NULL. Предупреждения могут создаваться при использовании выражения, которое может быть нулевым, в тех случаях, когда требуется ненулевое значение.
8.9.3 Ссылочные типы, допускающие значение NULL
Ссылочный тип формы T?
(например string?
) является типом ссылки, допускающего значение NULL. Состояние по умолчанию nullable-переменной — возможно null. Примечания ?
указывает намерение, которое переменные этого типа могут иметь значение NULL. Компилятор может распознать эти намерения для выдачи предупреждений. Если контекст аннотаций, допускающих значение NULL, отключен, использование этой аннотации может вызвать предупреждение.
Контекст, допускающий значение NULL, 8.9.4
8.9.4.1 Общие
Каждая строка исходного кода имеет контекст, допускающий значение NULL. Аннотации и флаги предупреждений управляют контекстом, допускающим значение NULL, для аннотаций (§8.9.4.3) и предупреждений (§8.9.4.4), соответственно. Каждый флаг можно включить или отключить. Компилятор может использовать статический анализ потока для определения состояния NULL любой ссылочной переменной. Статус ссылочной переменной (§8.9.5) либо не равен null, может быть null, или может быть по умолчанию.
Контекст, допускающий значение NULL, может быть указан в исходном коде с помощью директив null (§6.5.9) и (или) через определенный механизм реализации, внешний для исходного кода. Если используются оба подхода, директивы, допускающие значение NULL, заменяют параметры, сделанные через внешний механизм.
Состояние по умолчанию контекста, допускающего значение NULL, определяется реализацией.
В рамках этой спецификации предполагается, что весь код C#, который не содержит директивы для контроля допустимости NULL или в отношении которого не сделано никакого заявления о текущем состоянии этого контекста, должен быть скомпилирован с использованием контекста допустимости NULL, где аннотации и предупреждения включены.
Заметка: Контекст, допускающий значение NULL, в котором оба флага отключены, соответствуют предыдущему стандартному поведению ссылочных типов. конечная заметка
Отключение режима Nullability 8.9.4.2
Если флаги предупреждений и заметок отключены, контекст, допускающий значение NULL, отключен.
Если контекст, допускающий значение NULL, отключен:
- Предупреждение не создается при инициализации переменной ненататированного ссылочного типа или присвоении значения
null
. - Предупреждение не создается, когда переменная ссылочного типа, которая, возможно, имеет нулевое значение.
- Для любого ссылочного типа
T
заметка?
вT?
создает сообщение и типT?
совпадает с типомT
. - Для любого ограничения параметра типа
where T : C?
, аннотация?
вC?
создает сообщение, и типC?
совпадает сC
. - Для любого ограничения параметра типа
where T : U?
, аннотация?
вU?
создает сообщение, и типU?
совпадает сU
. - Универсальное ограничение
class?
создает предупреждение. Параметр типа должен быть ссылочным типом.Примечание. Это сообщение характеризуется как "информационный", а не "предупреждение", чтобы не спутать его с состоянием параметра предупреждения, допускающего значение NULL, которое не связано. конечная заметка
- Оператор игнорирования null (
!
) не имеет эффекта.
Пример:
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored
заключительный пример
8.9.4.3 Аннотации, допускающие NULL
Если флаг предупреждения отключен и флаг аннотаций включен, контекст, допускающий значение NULL, является аннотациями.
Если контекст, допускающий значение NULL, содержит заметки:
- Для любого ссылочного типа
T
в заметке?
аннотацияT?
указывает, чтоT?
является типом, допускающим значение NULL, тогда как ненатированныйT
не допускает значение NULL. - Предупреждения диагностики, связанные с возможностью null, не создаются.
- Оператор null-прощающий
!
(§12.8.9) может изменить проанализированное состояние NULL своего операнда и какие диагностические предупреждения создаются при компиляции.
Пример:
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings
заключительный пример
8.9.4.4 Предупреждения о нулевых значениях
Если флажок предупреждения включен и флаг заметок отключен, контекст, допускающий значение NULL, вызывает предупреждения.
При контексте предупреждениях, допускающем NULL, компилятор может выдавать диагностику в следующих случаях:
- Ссылочная переменная, которая, как было определено, может быть null, разыменована.
- Ссылочная переменная типа, не допускающего значения NULL, назначается выражению, которое может быть null.
-
?
используется для отмечания типа ссылок, допускающих значение NULL. - Оператор
!
, игнорирующий null (§12.8.9), используется для задания состояния null своего операнда как не null.
Пример:
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed
заключительный пример
Включение 8.9.4.5 Nullable
Если включен флаг предупреждения и флаг заметок, контекст, допускающий значение NULL, включен.
Если включен контекст, допускающий значение NULL, выполните следующие действия.
- Для любого ссылочного типа
T
аннотация?
вT?
делаетT?
типом, допускающим значение NULL, в то время как не отмеченныйT
является типом, не допускающим значение NULL. - Компилятор может использовать статический анализ потока для определения состояния NULL любой ссылочной переменной. При включенных предупреждениях о нулевых значениях, нулевое состояние ссылочной переменной (§8.9.5) может быть не null, возможно null иливозможно, значение по умолчанию и
- Оператор, допускающий null
!
(§12.8.9) задает состояние NULL своего операнда как не равным NULL. - Компилятор может выдавать предупреждение, если значение NULL параметра типа не соответствует значению NULL соответствующего аргумента типа.
8.9.5 Nullabilities и null-состояния
8.9.5.1 Общие
Компилятор не требуется для выполнения статического анализа и не требуется для создания каких-либо диагностических предупреждений, связанных с возможностью null.
Оставшаяся часть этого подклауза является условно нормативной.
Анализ потока 8.9.5.2
Компилятор, который создает предупреждения диагностики, соответствует этим правилам.
Каждое выражение имеет одно из трех состояний NULL:
- может иметь значение NULL: значение выражения может иметь значение NULL.
- Может быть, по умолчанию: значение выражения может оцениваться по умолчанию для этого типа.
- не null: значение выражения не равно NULL.
Состояние null выражения по умолчанию определяется его типом и состоянием флага заметок при объявлении:
- По умолчанию состояние null для nullable ссылочного типа равно:
- Может быть нулевым значением, если его объявление находится в тексте, где включен переключатель аннотаций.
- Не является NULL, если его объявление находится в тексте, где флаг аннотаций отключен.
- Состояние null по умолчанию ссылочного типа, не допускающего значения NULL, не равно NULL.
Заметка: Состояние по умолчанию может быть использовано с параметрами типа без ограничений, когда тип является не допускающим значение NULL, например
string
, и выражениеdefault(T)
представляет собой значение NULL. Так как значение NULL не находится в домене для типа, отличного от null, состояние может быть по умолчанию. конечная заметка
Диагностическое сообщение может быть создано, если переменная (§9.2.1) ненулевого ссылочного типа инициализируется или присваивается выражению, которое может быть null, в случае, если эта переменная объявлена в тексте, где включен флаг аннотации.
Пример. Рассмотрим следующий метод, в котором параметр имеет значение NULL, и это значение назначается типу, не допускающим значение NULL:
#nullable enable public class C { public void M(string? p) { // Warning: Assignment of maybe null value to non-nullable variable string s = p; } }
Компилятор может выдавать предупреждение, в котором параметр, который может иметь значение NULL, назначается переменной, которая не должна иметь значение NULL. Если параметр проверяется на null перед назначением, компилятор может использовать это в анализе состояния null и не выдавать предупреждение.
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // No warning // Use s } } }
заключительный пример
Компилятор может обновить состояние NULL переменной в рамках его анализа.
Пример: компилятор может обновить состояние на основе любых инструкций в программе:
#nullable enable public void M(string? p) { int length = p.Length; // Warning: p is maybe null string s = p; // No warning. p is not null if (s != null) { int l2 = s.Length; // No warning. s is not null } int l3 = s.Length; // Warning. s is maybe null }
В предыдущем примере компилятор может решить, что после оператора
int length = p.Length;
нулевое состояниеp
считается ненулевым. Если бы это было null, то это вызвало быNullReferenceException
исключение. Это похоже на поведение, если код был предшествованif (p == null) throw NullReferenceException();
, за исключением того, что код, просто написан, может привести к предупреждению, цель которого заключается в том, чтобы предупреждать, что исключение может быть брошено неявно. заключительный пример
Далее в методе код проверяет, что s
не является пустой ссылкой. Состояние NULL s
может измениться на значение NULL после закрытия флажка NULL. Компилятор может сделать вывод, что s
может быть null, поскольку код был написан с предположением, что он может быть null. Как правило, если код содержит проверку NULL, компилятор может определить, что значение может быть null:
Пример. Каждое из следующих выражений включает в себя некоторую форму проверки null. Состояние null
o
может измениться с не null на возможно null после каждой из этих инструкций:#nullable enable public void M(string s) { int length = s.Length; // No warning. s is not null _ = s == null; // Null check by testing equality. The null state of s is maybe null length = s.Length; // Warning, and changes the null state of s to not null _ = s?.Length; // The ?. is a null check and changes the null state of s to maybe null if (s.Length > 4) // Warning. Changes null state of s to not null { _ = s?[4]; // ?[] is a null check and changes the null state of s to maybe null _ = s.Length; // Warning. s is maybe null } }
Автоматические свойства и объявления событий, аналогичных полям, используют создаваемое компилятором скрытое поле. Анализ состояния NULL может выводить, что назначение событию или свойству является назначением для созданного компилятором поля резервной копии.
пример: компилятор может определить, что запись автоматического свойства или события, подобного полю, записывает соответствующее поле резервной копии компилятора. Состояние NULL свойства соответствует значению резервного поля.
class Test { public string P { get; set; } public Test() {} // Warning. "P" not set to a non-null value. static void Main() { var t = new Test(); int len = t.P.Length; // No warning. Null state is not null. } }
В предыдущем примере конструктор не устанавливает
P
в ненулевое значение, и компилятор может выдавать предупреждение. Предупреждение отсутствует при доступе к свойствуP
, так как его тип является ссылочным типом, не допускающим значения null. заключительный пример
Компилятор может рассматривать свойство (§15.7) как переменную с состоянием или как независимые методы доступа (§15.7.3).
примере: компилятор может выбрать, изменяет ли запись в свойство нулевое состояние чтения этого свойства, или изменяет ли чтение свойства нулевое состояние этого свойства.
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; // No warning. A compiler can assume property is stateful } } }
В предыдущем примере поддерживающее поле для
DisappearingProperty
устанавливается в NULL при чтении. Однако компилятор может предположить, что чтение свойства не изменяет состояние NULL этого выражения. заключительный пример
Компилятор может использовать любое выражение, которое разыменовывает переменную, свойство или событие, чтобы изменить состояние null на не равное null. Если бы значение было null, выражение разыменовки вызвало бы NullReferenceException
:
Пример:
public class C { private C? child; public void M() { _ = child.child.child; // Warning. Dereference possible null value var greatGrandChild = child.child.child; // No warning. } }
заключительный пример
8.9.5.3 Преобразования типов
Компилятор, который создает предупреждения диагностики, соответствует этим правилам.
Заметка: Различия в аннотациях нулевой допустимости верхнего уровня или вложенных аннотациях в типах не влияют на то, возможно ли преобразование между типами, так как нет семантической разницы между ссылочным типом, не допускающим значений NULL, и соответствующим типом, допускающим значения NULL (§8.9.1). конечная заметка
Компилятор может выдавать предупреждение, если аннотации нулевой допустимости различаются между двумя типами, на верхнем уровне или во вложенном контексте, при сужающем преобразовании.
Пример. Типы, отличающиеся в заметках верхнего уровня
#nullable enable public class C { public void M1(string p) { _ = (string?)p; // No warning, widening } public void M2(string? p) { _ = (string)p; // Warning, narrowing _ = (string)p!; // No warning, suppressed } }
заключительный пример
Пример. Типы, отличающиеся в вложенных заметках nullability
#nullable enable public class C { public void M1((string, string) p) { _ = ((string?, string?))p; // No warning, widening } public void M2((string?, string?) p) { _ = ((string, string))p; // Warning, narrowing _ = ((string, string))p!; // No warning, suppressed } }
заключительный пример
Компилятор может следовать правилам дисперсии интерфейса (§18.2.3.3), дисперсии делегатов (§20.4) и ковариации массива (§17.6) при определении того, следует ли выдавать предупреждение о преобразовании типов.
#nullable enable public class C { public void M1(IEnumerable<string> p) { IEnumerable<string?> v1 = p; // No warning } public void M2(IEnumerable<string?> p) { IEnumerable<string> v1 = p; // Warning IEnumerable<string> v2 = p!; // No warning } public void M3(Action<string?> p) { Action<string> v1 = p; // No warning } public void M4(Action<string> p) { Action<string?> v1 = p; // Warning Action<string?> v2 = p!; // No warning } public void M5(string[] p) { string?[] v1 = p; // No warning } public void M6(string?[] p) { string[] v1 = p; // Warning string[] v2 = p!; // No warning } }
заключительный пример
Компилятор может выдавать предупреждение, если наличие NULL отличается в любом из направлений в типах, которые не допускают ковариантное преобразование.
#nullable enable public class C { public void M1(List<string> p) { List<string?> v1 = p; // Warning List<string?> v2 = p!; // No warning } public void M2(List<string?> p) { List<string> v1 = p; // Warning List<string> v2 = p!; // No warning } }
заключительный пример
Конец условно нормативных текстов
ECMA C# draft specification