Поделиться через


8 типов

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

Все типы значений неявно наследуются от classSystem.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_typeE, значение по умолчанию 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, byteshortushortintuint, , longulongи .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 такой, что EmineEmax, где Cmax составляет не менее 1 × 10²⁸, Emin ≤ 0, и Emax ≥ 28. Тип decimal необязательно поддерживает знаковые нули, бесконечности или NaN.

А decimal представляется целым числом, масштабируемым по 10. Для decimals с абсолютным значением меньше 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, intuintlong или 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

Экземпляр, для которого HasValuetrue, считается не являющимся 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 одним из следующих способов:
    • Преобразование удостоверений (§10.2.2)
    • Неявное преобразование ссылок (§10.2.8)
    • Преобразование бокса (§10.2.9), при условии, что тип A является типом, не допускающим значения null.
    • Неявная ссылка или преобразование через упаковку или параметр типа из параметра типа 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 удовлетворить ограничение, введенное базойclassB<T>. В отличие от этого, classE не нуждаются в указании ограничения, так как 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.
  • Подписи, которые совпадают при замене сигнатурой dynamicobject , считаются одной и той же сигнатурой.
  • Тип 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
    }
}

заключительный пример

Конец условно нормативных текстов