Каким образом поведение 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?)
(а не бросать NullReferenceException
).
Поэтому я немного покопался и, конечно же, оказалось, что это поведение относится к Nullable<T>
тип, специально определенный в спецификациях C# и VB.NET, и не воспроизводимый в каких-либо пользовательских struct
(C#) или Structure
(VB.NET).
Вот почему я все еще в замешательстве.
Это конкретное поведение при упаковке и распаковке, по-видимому, невозможно реализовать вручную. Это работает только потому, что и C#, и VB.NET уделяют особое внимание Nullable<T>
тип.
Разве теоретически не возможно, чтобы существовал другой язык на основе CLI, где
Nullable<T>
не получили этого специального лечения? И не будетNullable<T>
типа поэтому проявляете разное поведение на разных языках?Как C# и VB.NET достигают такого поведения? Это поддерживается CLR? (То есть, позволяет ли CLR типу каким-то образом "переопределять" способ, которым он упакован, даже если C# и VB.NET сами это запрещают?)
Можно ли даже (в C# или VB.NET) поставить
Nullable<T>
какobject
?
3 ответа
Происходят две вещи:
1) Компилятор обрабатывает "null" не как нулевую ссылку, а как нулевое значение... нулевое значение для любого типа, в который он должен преобразовываться. В случае Nullable<T>
это просто значение, которое имеет значение False для HasValue
поле / свойство. Так что если у вас есть переменная типа int?
вполне возможно, что значение этой переменной будет null
- вам просто нужно изменить свое понимание того, что null
значит немного.
2) Бокс типа Nullable получает специальную обработку самой CLR. Это актуально в вашем втором примере:
int? i = new int?();
object x = i;
компилятор будет помещать любое значение типа Nullable в отличие от значений типа Nullable. Если значение не равно NULL, результат будет таким же, как и в боксе то же значение, что и значение необнуляемого типа, поэтому int?
со значением 5 упаковывается так же, как int
со значением 5 - "обнуляемость" теряется. Тем не менее, нулевое значение типа NULL может быть просто помещено в пустую ссылку, а не создавать объект вообще.
Это было введено в конце цикла CLR v2 по просьбе сообщества.
Это означает, что нет такого понятия, как "значение типа NULL-значения в штучной упаковке".
Вы правильно поняли: Nullable<T>
получает специальную обработку от компилятора, как в VB, так и в C#. Следовательно:
- Да. Языковой компилятор нуждается в особом случае
Nullable<T>
, - Компилятор изменяет использование
Nullable<T>
, Операторы просто синтаксический сахар. - Не то, что я знаю из.
Я задавал себе тот же вопрос, и я также ожидал иметь некоторый неявный оператор для Nullable<T>
в исходном коде.net Nullable, поэтому я посмотрел, что такое код IL, соответствующий int? a = null;
чтобы понять, что происходит за сценой:
код C#:
int? a = null;
int? a2 = new int?();
object a3 = null;
int? b = 5;
int? b2 = new int?(5);
IL-код (генерируется с помощью LINQPad 5):
IL_0000: nop
IL_0001: ldloca.s 00 // a
IL_0003: initobj System.Nullable<System.Int32>
IL_0009: ldloca.s 01 // a2
IL_000B: initobj System.Nullable<System.Int32>
IL_0011: ldnull
IL_0012: stloc.2 // a3
IL_0013: ldloca.s 03 // b
IL_0015: ldc.i4.5
IL_0016: call System.Nullable<System.Int32>..ctor
IL_001B: ldloca.s 04 // b2
IL_001D: ldc.i4.5
IL_001E: call System.Nullable<System.Int32>..ctor
IL_0023: ret
Мы видим, что смена компилятора int? a = null
что-то вроде int? a = new int?()
который сильно отличается от object a3 = null
, Очевидно, что Nullable s имеют специальную обработку компилятора.