8 типов
8.1 Общие
Типы языка C# делятся на две основные категории: ссылочные типы и типы значений. Как типы значений, так и ссылочные типы могут быть универсальными типами, которые принимают один или несколько параметров типа. Параметры типа могут назначать как типы значений, так и ссылочные типы.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) доступна только в небезопасном коде (§23).
Типы значений отличаются от ссылочных типов в переменных типов значений непосредственно содержат их данные, тогда как переменные ссылочных типов хранят ссылки на их данные, последний называется объектами. С ссылочными типами можно ссылаться на один и тот же объект и, следовательно, использовать операции с одной переменной, чтобы повлиять на объект, на который ссылается другая переменная. При использовании типов значений переменные имеют собственную копию данных, и для операций с одной из них нельзя повлиять на другую.
Примечание. Если переменная является ссылочным или выходным параметром, она не имеет собственного хранилища, но ссылается на хранилище другой переменной. В этом случае переменная ссылок или вне фактически является псевдонимом другой переменной, а не отдельной. конечная заметка
Система типов C#унифицировано таким образом, что значение любого типа можно рассматривать как объект. Каждый тип в C# является прямо или косвенно производным от типа класса object
, и этот тип object
является исходным базовым классом для всех типов. Чтобы значения ссылочного типа обрабатывались как объекты, им просто присваивается тип object
. Значения типов значений обрабатываются как объекты путем выполнения операций бокса и распаковки (§8.3.13).
Для удобства в этой спецификации некоторые имена типов библиотек записываются без использования их полной квалификации. Дополнительные сведения см. в разделе "C.5 ".
Типы ссылок 8.2
8.2.1 Общие
Ссылочный тип — это тип класса, тип интерфейса, тип массива, тип делегата или dynamic
тип. Для каждого ненулевого ссылочного типа существует соответствующий ссылочный тип, допускающий значение 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_expression s (§12.8.17.2).
Типы классов описаны в разделе §15.
Некоторые предопределенные типы классов имеют особое значение на языке C#, как описано в таблице ниже.
Тип класса | Description |
---|---|
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_typeавтоматически производны.
Конструкторы по умолчанию 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
значение . -
E
значение по умолчанию преобразуется0
в типE
.
- Для
-
Для struct_type значение по умолчанию — это значение, созданное путем установки всех полей типа значения в значение по умолчанию и всех полей
null
ссылочного типа. -
Для nullable_value_type значение по умолчанию является экземпляром, для которого
HasValue
свойство равно 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
и все операции, разрешенные в одном типе, разрешены в другом. конечная заметка
Все подписанные целочисленные типы представлены с помощью двух форматов дополнения.
Тип char
классифицируется как целочисленный тип, но отличается от других целочисленных типов двумя способами:
- Предопределенные неявные преобразования из других типов в
char
тип отсутствуют. В частности, хотяbyte
иushort
типы имеют диапазоны значений, которые полностью представляются с помощьюchar
типа, неявных преобразований из байтов, байтов илиushort
char
не существуют. - Константы типа должны быть записаны
char
как character_literal или как integer_literals в сочетании с приведением к типу 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 создаются недопустимыми операциями с плавающей запятой, например делением нуля на ноль.
- Конечный набор ненулевых значений формы × m × 2e, где s равно 1 или –1, а м и e определяются определенным типом с плавающей запятой: для
float
, 0< 2⁴ и –149 ≤ e ≤ 104, а дляdouble
, 0 << 2⁵⁵ ≤ 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)v × c × 10⁻e, где знак v равен 0 или 1, коэффициент c присваивается 0 ≤ cmax<, а масштабe — это то, что Emin ≤ e ≤ Emax, где Cmax составляет по крайней мере 1 × 10 ⁸, Emin ≤ 0, и Emax ≥ 28. Тип decimal
не обязательно поддерживает подписанные нули, определенные значения или naN.
А decimal
представляется целым числом, масштабируемым по 10. Для decimal
s с абсолютным значением меньше 1.0m
, значение точно равно 28-му десятичному разряду. Для decimal
s с абсолютным значением, превышающим или равным 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
Тип кортежа представляет упорядоченную последовательность значений фиксированной длины с необязательными именами и отдельными типами. Число элементов в типе кортежа называется его arity. Тип кортежа записывается (T1 I1, ..., Tn In)
с n ≥ 2, где идентификаторы I1...In
являются необязательными именами элементов кортежа.
Этот синтаксис является коротким для типа, созданного с помощью типов, которые должны быть набором универсальных типов структур, способных напрямую выразить типы T1...Tn
System.ValueTuple<...>
кортежей любого arity в диапазоне от двух до семи включительно.
Не нужно существовать System.ValueTuple<...>
объявление, которое напрямую соответствует arity любого типа кортежа с соответствующим числом параметров типа. Вместо этого кортежи с 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
имени, соответствующему их положению, а также по соответствующему имени элемента кортежа, если они присутствуют в типе. пример конца
Типы значений, допускающих значение NULL 8.3.12
Тип значения, допускающего значение NULL, может представлять все значения базового типа, а также дополнительное значение NULL. Записывается T?
тип значения, допускающий значение NULL, где 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. Экземпляр NULL имеет неопределенное значение. Попытка считывания Value
экземпляра NULL приводит System.InvalidOperationException
к возникновению ошибки. Процесс доступа к свойству Value экземпляра, допускающего значение NULL, называется распакуемой.
В дополнение к конструктору по умолчанию каждый тип T?
значения, допускающего значение NULL, имеет открытый конструктор с одним параметром типа T
. Если задано значение x
типа T
, вызов конструктора формы
new T?(x)
создает ненулевой экземпляр, T?
для которого Value
является x
свойство. Процесс создания экземпляра значения, отличного от NULL, для заданного значения называется оболочкой.
Неявные преобразования доступны из null
литерала T?
в (§10.2.7) и из T
T?
(§10.2.6).
Тип T?
значения, допускающий значение NULL, не реализует интерфейсы (§18). В частности, это означает, что он не реализует интерфейс, который выполняет базовый тип T
.
8.3.13 Бокс и распаковка
Концепция бокса и распаковки обеспечивает мост между value_typeи reference_types, разрешая преобразование любого значения 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_arguments '>'
;
type_arguments
: 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
открытый конструктор без параметров. Это удовлетворено, если одно из следующих значений имеет значение true:-
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
) можно использовать только с параметром типа, если параметр типа ограничен 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
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. Преобразование удостоверений существует среди ссылочного типа, допускающего значение NULL, и соответствующего ненулевого ссылочного типа (§10.2.2).
Существует две формы null для ссылочных типов:
-
значение NULL:
null
, допускающий значение NULL-reference. Его состояние NULL по умолчанию может иметь значение NULL. -
ненулевое значение: ненулевой ссылкой не следует назначать
null
значение. Его состояние NULL по умолчанию не равно NULL.
Примечание. Типы
R
иR?
представлены тем же базовым типом.R
Переменная этого базового типа может содержать ссылку на объект или значениеnull
, указывающее "нет ссылки". конечная заметка
Синтаксическое различие между типом ссылок, допускаемым значением NULL, и соответствующим ненулевом ссылочным типом позволяет компилятору создавать диагностика. Компилятор должен разрешить nullable_type_annotation , как определено в §8.2.1. Диагностика должны быть ограничены предупреждениями. Ни наличие или отсутствие заметок, допускающих значение NULL, ни состояние контекста, допускающего значение NULL, не может изменять время компиляции или поведение среды выполнения программы, за исключением изменений в любых диагностических сообщениях, созданных во время компиляции.
Типы ссылок, не допускающие значение NULL, 8.9.2
Ссылочный тип, не допускающий значения NULL, является ссылочным типом формыT
, где T
имя типа. Значение null-state для переменной, не допускающей значения NULL, не равно NULL. Предупреждения могут создаваться при использовании выражения, которое может быть null , где требуется ненульное значение.
Ссылочные типы, допускающие значение NULL 8.9.3
Ссылочный тип формы T?
(например string?
) является типом ссылки, допускающего значение NULL. Значение null по умолчанию переменной, допускающей значение NULL, может быть, равно null. Примечания ?
указывает намерение, которое переменные этого типа могут иметь значение NULL. Компилятор может распознать эти намерения для выдачи предупреждений. Если контекст заметки, допускающий значение NULL, отключен, эта заметка может создать предупреждение.
Контекст, допускающий значение NULL 8.9.4
8.9.4.1 Общие
Каждая строка исходного кода имеет контекст, допускающий значение NULL. Заметки и предупреждения для заметок, допускающих значение NULL контекста, допускающих значение NULL (§8.9.4.3), и предупреждения, допускающие значение NULL (§8.9.4.4), соответственно. Каждый флаг можно включить или отключить. Компилятор может использовать статический анализ потока для определения состояния NULL любой ссылочной переменной. Состояние NULL переменной ссылочной переменной (§8.9.5) либо не равно NULL, может быть null, или, возможно, по умолчанию.
Контекст, допускающий значение NULL, может быть указан в исходном коде с помощью директив null (§6.5.9) и (или) через определенный механизм реализации, внешний для исходного кода. Если используются оба подхода, директивы, допускающие значение NULL, заменяют параметры, сделанные через внешний механизм.
Состояние по умолчанию контекста, допускающего значение NULL, определяется реализацией.
В рамках этой спецификации предполагается, что весь код C#, не содержащий директивы NULL, или о том, какие инструкции не выполняются в отношении текущего состояния контекста, допускающего значение NULL, должны быть скомпилированы с помощью контекста, допускающего значение NULL, в котором включены как заметки, так и предупреждения.
Примечание. Контекст, допускающий значение NULL, в котором оба флага отключены, соответствуют предыдущему стандартному поведению для ссылочных типов. конечная заметка
Отключение 8.9.4.2 Nullable
Если флаги предупреждений и заметок отключены, контекст , допускающий значение NULL, отключен.
Если контекст, допускающий значение NULL, отключен:
- Предупреждение не создается при инициализации переменной ненататированного ссылочного типа или присвоении значения
null
. - Предупреждение не создается, если переменная ссылочного типа, которая, возможно, имеет значение NULL.
- Для любого ссылочного типа
T
заметка?
вT?
создает сообщение и типT?
совпадает с типомT
. - Для любого ограничения
where T : C?
параметра типа заметка?
вC?
создает сообщение, а тип совпадает с типомC?
C
. - Для любого ограничения
where T : U?
параметра типа заметка?
вU?
создает сообщение, а тип совпадает с типомU?
U
. - Универсальное ограничение
class?
создает предупреждение. Параметр типа должен быть ссылочным типом.Примечание. Это сообщение характеризуется как "информационный", а не "предупреждение", чтобы не спутать его с состоянием параметра предупреждения, допускающего значение NULL, которое не связано. конечная заметка
- Оператор
!
прощения null (§12.8.9) не действует.
Пример:
#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
пример конца
Заметки, допускающие значение NULL, 8.9.4.3
Если флаг предупреждения отключен и флаг заметок включен, контекст, допускающий значение 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
пример конца
Предупреждения, допускающие значение NULL, 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, в то время как unannotatedT
не допускает значение NULL. - Компилятор может использовать статический анализ потока для определения состояния NULL любой ссылочной переменной. Если включены предупреждения, допускающие значение NULL, состояние null переменной ссылочной переменной (§8.9.5) не равно null, может быть null, или, возможно, по умолчанию и
- Оператор
!
прощения null (§12.8.9) задает состояние NULL своего операнда не равно NULL. - Компилятор может выдавать предупреждение, если значение NULL параметра типа не соответствует значению NULL соответствующего аргумента типа.
8.9.5 Nullabilities и null-состояния
Компилятор не требуется для выполнения статического анализа и не требуется для создания каких-либо диагностических предупреждений, связанных с возможностью null.
Оставшаяся часть этого подклауза является условно нормативным.
Компилятор, который создает предупреждения диагностики, соответствует этим правилам.
Каждое выражение имеет одно из трех состоянийNULL:
- может иметь значение NULL: значение выражения может иметь значение NULL.
- Может быть, по умолчанию: значение выражения может оцениваться по умолчанию для этого типа.
- не null: значение выражения не равно NULL.
Состояние null выражения по умолчанию определяется его типом и состоянием флага заметок при объявлении:
- Состояние null по умолчанию для ссылочного типа, допускающего значение NULL, равное следующим:
- Может быть, null, если его объявление находится в тексте, где включен флаг примечаний.
- Не имеет значения 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
нет предупреждения, так как тип свойства является непустым ссылочным типом. пример конца
Компилятор может рассматривать свойство (§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. } }
пример конца
Конец условно нормативных текстов
ECMA C# draft specification