Есть ли разница между "x is int?" И "x is int" в С#?
class C<T> where T : struct {
bool M1(object o) => o is T;
bool M2(object o) => o is T?;
}
Два вышеприведенных метода ведут себя одинаково, как при передаче null
reference, так и в боксе T
. Однако сгенерированный код MSIL немного отличается:
.method private hidebysig instance bool M1(object o) cil managed {
.maxstack 8
IL_0000: ldarg.1
IL_0001: isinst !T
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: ret
}
против
.method private hidebysig instance bool M2(object o) cil managed {
.maxstack 8
IL_0000: ldarg.1
IL_0001: isinst valuetype [mscorlib]System.Nullable`1<!T>
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: ret
}
Как вы можете видеть, выражение o is T?
фактически выполняет проверку типа для типа Nullable<T>
, несмотря на то, что типы с нулевым значением специально обрабатываются с помощью CLR, так что С# представляет значение в коробке T?
как null
reference (if T?
не имеет значения) или значение в коробке T
. Кажется невозможным получить поле типа Nullable<T>
в чистом С# или, возможно, даже в С++/CLI (поскольку runtime обрабатывает код операции box
для поддержки этого "T?
= > T
box/null
" бокса).
Я что-то упустил или o is T?
практически эквивалентен o is T
в С#?
Ответы
Ответ 1
В соответствии со спецификацией (выделение), в E is T
, типы с нулевыми значениями T
и соответствующие типы с нулевым значением обрабатываются одинаково:
7.10.10 Оператор is
Оператор is
используется для динамической проверки того, совместим ли тип времени выполнения с данным типом. Результат операции E is T
, где E
является выражением, а T
является типом, является логическим значением, указывающим, может ли E
быть успешно преобразован в тип T
посредством ссылочного преобразования, преобразования бокса, или преобразование распаковки. Операция оценивается следующим образом, после того, как аргументы типа были заменены всеми параметрами типа:
-
Если E
является анонимной функцией, возникает ошибка времени компиляции
-
Если E
- это группа методов или нулевой литерал, если тип E
является ссылочным типом или типом NULL, а значение E равно null, результат равен false.
-
В противном случае пусть D
представляет динамический тип E
следующим образом:
- Если тип
E
является ссылочным типом, D
является типом времени выполнения справки экземпляра E
. -
Если тип E
является нулевым типом, D
является базовым типом этого типа с нулевым значением.
-
Если тип E
- тип значения, не равный nullable, D
является типом E
.
-
Результат операции зависит от D
и T
следующим образом:
- Если
T
является ссылочным типом, результат имеет значение true, если D
и T
являются одним и тем же типом, если D
является ссылочным типом и неявным эталонным преобразованием от D
до T
существует, или если D
- тип значения, и существует преобразование бокса из D
в T
. - Если
T
- тип с нулевым значением, результат будет истинным, если D
является базовым типом T
. - Если
T
- тип значения, не равный nullable, результат будет истинным, если D
и T
являются одним и тем же типом. - В противном случае результат будет ложным.
Ответ 2
Рассмотрим этот общий метод:
static bool Is<T>(object arg)
{
return arg is T;
}
Важнейшая часть этого метода скомпилируется в isinst !!T
. Теперь вы ожидаете, что Is<int?>(arg)
будет вести себя точно так же, как arg is int?
, не так ли? Чтобы обеспечить эту точную согласованность, компилятор С# должен испускать один и тот же CIL во всех случаях, и пусть бремя обработки типов с нулевым значением лежит в CLR.
Поведение CLR можно посмотреть в исходном коде coreclr на GitHub: IsInst, ObjIsInstanceOf. Как вы можете видеть во второй функции, если тип является нулевым представлением типа аргумента, он возвращает true.
разрешить приведение объекта типа T к Nullable (они имеют одинаковое представление)
Да, текущее поведение этих инструкций одинаков, поэтому изменение is T?
на is T
не будет иметь никакого значения (даже для аргумента null
), но чтобы справиться с любыми возможными будущими изменениями в CLR, компилятор С# не может принять это решение (хотя вероятность изменения поведения isinst
близка к нулю).
Nullable типы действительно замечательные вещи в .NET, особенно из-за их конкретной обработки в среде CLR, несмотря на то, что они не имеют специального синтаксиса в CIL (для совместимости). Фактически нет нормального способа боксирования типа с нулевым типом в его фактическом типе, а не в базовом, поскольку это приведет к возникновению несоответствий в приведениях и проверках (нулевая ссылка равна нулевому типу с нулевым значением или нет?). Тем не менее, вы можете обмануть CLR, подумав, что вы даете ему тип с нулевым значением (не то, что вам нужно).