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


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

Все типы значений classSystem.ValueTypeнеявно наследуются от класса, который, в свою очередь, наследует от класса object. Для любого типа невозможно наследовать от типа значения, а типы значений таким образом неявно запечатаны (§15.2.2.3).

Обратите внимание, что System.ValueType это не value_type. Скорее, это class_type , от которого все value_typeавтоматически производны.

Конструкторы по умолчанию 8.3.3

Все типы значений неявно объявляют открытый конструктор экземпляра без параметров, называемый конструктором по умолчанию. Конструктор по умолчанию возвращает нулевой инициализированный экземпляр, известный как значение по умолчанию для типа значения:

  • Для всех simple_typeзначение по умолчанию — это значение, созданное битовой шаблоном всех нулей:
    • Для sbyte, byte, , shortushortintuintlongи 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) создает тот же результат, что и конструктор по умолчанию. конечная заметка

Пример. В приведенном ниже коде переменные и все инициализированы ijk до нуля.

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 типа, неявных преобразований из байтов, байтов или ushortchar не существуют.
  • Константы типа должны быть записаны 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 ≤ eEmax, где Cmax составляет по крайней мере 1 × 10 ⁸, Emin ≤ 0, и Emax ≥ 28. Тип decimal не обязательно поддерживает подписанные нули, определенные значения или naN.

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

Тип кортежа представляет упорядоченную последовательность значений фиксированной длины с необязательными именами и отдельными типами. Число элементов в типе кортежа называется его arity. Тип кортежа записывается (T1 I1, ..., Tn In) с n ≥ 2, где идентификаторы I1...In являются необязательными именами элементов кортежа.

Этот синтаксис является коротким для типа, созданного с помощью типов, которые должны быть набором универсальных типов структур, способных напрямую выразить типы T1...TnSystem.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

Экземпляр, для которого HasValuetrue , как утверждается, не имеет значения 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) и из TT? (§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 представить это ограничение аргументами указанного типа, замененными любыми параметрами типа, которые отображаются в ограничении. Для удовлетворения ограничения следует учитывать, что тип преобразуется в тип AC по одному из следующих типов:
    • Преобразование удостоверений (§10.2.2)
    • Неявное преобразование ссылок (§10.2.8)
    • Преобразование бокса (§10.2.9), при условии, что этот тип является типом A ненулевого значения.
    • Неявная ссылка, преобразование параметров бокса или типа из параметра ACтипа в .
  • Если ограничение является ограничением ссылочного типа (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 удовлетворить ограничение, введенное базой 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) можно использовать только с параметром типа, если параметр типа ограничен 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 и dynamic
    • между созданными типами, которые совпадают при замене dynamic на object
    • между типами кортежей, которые совпадают при замене dynamic на object
  • Неявные и явные преобразования в и из object нее применяются и из 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, shortushortintuintlongulongcharfloatdoubledecimalили .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, в то время как unannotated T не допускает значение 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, например stringdefault(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. 
    }
}

пример конца

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