Что-то мне пришло в голову раньше сегодня, и я почесал голову.
Любую переменную типа 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?