Каким может быть поведение Nullable <T> в боксе/распаковке?
Что-то мне пришло в голову раньше сегодня, и я почесал голову.
Любую переменную типа Nullable<T>
можно присвоить null
. Например:
int? i = null;
Сначала я не мог понять, как это возможно, без какого-либо определения неявного преобразования от object
до Nullable<T>
:
public static implicit operator Nullable<T>(object box);
Но вышеприведенный оператор явно не существует, как если бы он это сделал, тогда также должно было бы быть законным, по крайней мере, во время компиляции (чего это не так):
int? i = new object();
Тогда я понял, что, возможно, тип Nullable<T>
может определить неявное преобразование в какой-либо произвольный ссылочный тип, который никогда не может быть создан, например:
public abstract class DummyBox
{
private DummyBox()
{ }
}
public struct Nullable<T> where T : struct
{
public static implicit operator Nullable<T>(DummyBox box)
{
if (box == null)
{
return new Nullable<T>();
}
// This should never be possible, as a DummyBox cannot be instantiated.
throw new InvalidCastException();
}
}
Однако это не объясняет, что произошло со мной следующим: если свойство HasValue
false
для любого значения Nullable<T>
, тогда это значение будет помечено как null
:
int? i = new int?();
object x = i; // Now x is null.
Кроме того, если HasValue
- true
, тогда значение будет помещаться как T
, а не T?
:
int? i = 5;
object x = i; // Now x is a boxed int, NOT a boxed Nullable<int>.
Но это, по-видимому, подразумевает, что существует неявное преобразование с Nullable<T>
в object
:
public static implicit operator object(Nullable<T> value);
Это явно не так, поскольку object
является базовым классом для всех типов, а пользовательские неявные преобразования в/из базовых типов являются незаконными (также они должны быть).
Кажется, что object x = i;
должен помещать i
как любой другой тип значения, так что x.GetType()
даст тот же результат, что и typeof(int?)
(а не бросает a NullReferenceException
).
Итак, я вырыл бит и, конечно же, выяснилось, что это поведение специфично для типа Nullable<T>
, специально определенного как в спецификациях С#, так и в VB.NET, и не воспроизводимо в любом пользовательском struct
(С#) или Structure
(VB.NET).
Вот почему я все еще смущен.
Это конкретное поведение в боксе и распаковке представляется невозможным для реализации вручную. Он работает только потому, что и С#, и VB.NET обеспечивают специальное обращение к типу Nullable<T>
.
-
Не теоретически возможно, что может существовать другой язык на основе CLI, где Nullable<T>
не было предоставлено это специальное лечение? И не будет ли тип Nullable<T>
проявлять различное поведение на разных языках?
-
Как С# и VB.NET достигают такого поведения? Поддерживается ли это CLR? (То есть, CLR позволяет типу каким-то образом "переопределять" способ, которым он помещен в коробку, даже если сами С# и VB.NET запрещают его?)
-
Возможно ли (в С# или VB.NET) вставить a Nullable<T>
в качестве object
?
Ответы
Ответ 1
Происходит две вещи:
1) Компилятор рассматривает "null" не как нулевую ссылку, а как нулевое значение... нулевое значение для любого типа, в котором ему нужно преобразовать. В случае Nullable<T>
это просто значение, которое имеет значение False для поля/свойства HasValue
. Поэтому, если у вас есть переменная типа int?
, вполне возможно, что значение этой переменной будет null
- вам просто нужно изменить свое понимание того, что null
означает немного.
2) Бокс-типа с нулевым значением получает специальное лечение самой CLR. Это актуально в вашем втором примере:
int? i = new int?();
object x = i;
компилятор будет указывать любое значение типа NULL по-разному на значения, отличные от значения NULL. Если значение не равно null, результат будет таким же, как в боксе, то же самое значение, что и значение типа с нулевым значением - так что int?
со значением 5 будет вставлен так же, как int
со значением 5 - "недействительность" теряется. Однако нулевое значение типа с нулевым значением помещается в поле только нулевой ссылке, а не создает объект вообще.
Это было введено в конце цикла CLR v2 по просьбе сообщества.
Это означает, что нет такой вещи, как "значение с нулевым значением в коробке".
Ответ 2
Вы правильно поняли: Nullable<T>
получает специальную обработку от компилятора, как в VB, так и в С#. Поэтому:
- Да. Компилятор языка должен иметь специальный случай
Nullable<T>
.
- Использование рефакторов компилятора
Nullable<T>
. Операторы - это просто синтаксический сахар.
- Не то, чтобы я знал.