Каким образом поведение 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> тип.

  1. Разве теоретически не возможно, чтобы существовал другой язык на основе CLI, где Nullable<T> не получили этого специального лечения? И не будет Nullable<T> типа поэтому проявляете разное поведение на разных языках?

  2. Как C# и VB.NET достигают такого поведения? Это поддерживается CLR? (То есть, позволяет ли CLR типу каким-то образом "переопределять" способ, которым он упакован, даже если C# и VB.NET сами это запрещают?)

  3. Можно ли даже (в 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#. Следовательно:

  1. Да. Языковой компилятор нуждается в особом случае Nullable<T>,
  2. Компилятор изменяет использование Nullable<T>, Операторы просто синтаксический сахар.
  3. Не то, что я знаю из.

Я задавал себе тот же вопрос, и я также ожидал иметь некоторый неявный оператор для 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 имеют специальную обработку компилятора.

Другие вопросы по тегам