Como é possível o comportamento de boxe / desmarcação de Nullable ?

Algo me ocorreu mais cedo hoje que me fez coçar a cabeça.

Qualquer variável do tipo Nullable pode ser atribuída a null . Por exemplo:

 int? i = null; 

No começo eu não conseguia ver como isso seria possível sem definir de alguma forma uma conversão implícita do object para Nullable :

 public static implicit operator Nullable(object box); 

Mas o operador acima claramente não existe, como se ele fizesse então o seguinte também teria que ser legal, pelo menos em tempo de compilation (o que não é):

 int? i = new object(); 

Então eu percebi que talvez o tipo Nullable pudesse definir uma conversão implícita para algum tipo de referência arbitrária que nunca poderia ser instanciado, assim:

 public abstract class DummyBox { private DummyBox() { } } public struct Nullable where T : struct { public static implicit operator Nullable(DummyBox box) { if (box == null) { return new Nullable(); } // This should never be possible, as a DummyBox cannot be instantiated. throw new InvalidCastException(); } } 

No entanto, isso não explica o que me ocorreu a seguir: se a propriedade HasValue for false para qualquer valor Nullable , esse valor será encaixotado como null :

 int? i = new int?(); object x = i; // Now x is null. 

Além disso, se HasValue for true , o valor será encheckboxdo como um T vez de um T? :

 int? i = 5; object x = i; // Now x is a boxed int, NOT a boxed Nullable. 

Mas isso parece implicar que há uma conversão implícita personalizada de Nullable para object :

 public static implicit operator object(Nullable value); 

Esse claramente não é o caso, já que o object é uma class base para todos os tipos, e as conversões implícitas definidas pelo usuário para / de tipos de base são ilegais (assim como deveriam ser).

Parece que o object x = i; deve checkbox i gosto de qualquer outro tipo de valor, para que x.GetType() produziria o mesmo resultado como typeof(int?) (em vez de lançar um NullReferenceException ).

Então eu pesquisei um pouco e, com certeza, esse comportamento é específico do tipo Nullable , especialmente definido nas especificações C # e VB.NET, e não reproduzível em nenhuma struct definida pelo usuário (C #) ou Structure (vb.net).

Aqui está porque eu ainda estou confuso.

Esse comportamento específico de boxe e desempacotamento parece ser impossível de implementar manualmente. Isso só funciona porque o C # e o VB.NET dão um tratamento especial ao tipo Nullable .

  1. Não é teoricamente possível que uma linguagem diferente baseada em CLI possa existir onde Nullable não recebeu este tratamento especial? E o tipo Nullable não apresentaria comportamento diferente em diferentes idiomas?

  2. Como C # e VB.NET conseguem esse comportamento? É apoiado pelo CLR? (Isto é, o CLR permite que um tipo de alguma forma “substitua” a maneira como ele é encaixotado, mesmo que C # e VB.NET o proíbam?)

  3. É possível (em C # ou VB.NET) colocar um Nullable como object ?

Há duas coisas acontecendo:

1) O compilador trata “nulo” não como uma referência nula, mas como um valor nulo … o valor nulo para qualquer tipo que precise converter. No caso de um Nullable é apenas o valor que tem False para o campo / propriedade de HasValue . Então, se você tem uma variável do tipo int? , é bem possível que o valor dessa variável seja null – você só precisa mudar sua compreensão do que significa null um pouco.

2) Tipos anuláveis ​​de boxe recebem tratamento especial pelo próprio CLR. Isso é relevante em seu segundo exemplo:

  int? i = new int?(); object x = i; 

o compilador encheckboxrá qualquer valor de tipo anulável de maneira diferente para valores de tipo não anuláveis. Se o valor não for nulo, o resultado será o mesmo que encheckboxr o mesmo valor que um valor de tipo não anulável – portanto, um int? com o valor 5 fica empacotado da mesma maneira que um int com valor 5 – a “nulidade” é perdida. No entanto, o valor nulo de um tipo anulável é encaixotado apenas na referência nula, em vez de criar um object.

Isto foi introduzido no final do ciclo CLR v2, a pedido da comunidade.

Isso significa que não existe um “valor do tipo valor anulável”.

Você acertou: Nullable recebe tratamento especial do compilador, tanto em VB quanto em C #. Assim sendo:

  1. Sim. O compilador de linguagem precisa de um caso especial Nullable .
  2. O uso de refatoradores do compilador de Nullable . Os operadores são apenas açúcar sintático.
  3. Não que eu saiba.

Eu estava me perguntando a mesma pergunta e eu também estava esperando ter algum operador implícito para Nullable no código-fonte Nullable .net, então eu olhei para o que é o código IL correspondente ao int? a = null; int? a = null; para entender o que está acontecendo nos bastidores:

c # code:

 int? a = null; int? a2 = new int?(); object a3 = null; int? b = 5; int? b2 = new int?(5); 

Código IL (gerado com o LINQPad 5):

 IL_0000: nop IL_0001: ldloca.s 00 // a IL_0003: initobj System.Nullable IL_0009: ldloca.s 01 // a2 IL_000B: initobj System.Nullable IL_0011: ldnull IL_0012: stloc.2 // a3 IL_0013: ldloca.s 03 // b IL_0015: ldc.i4.5 IL_0016: call System.Nullable..ctor IL_001B: ldloca.s 04 // b2 IL_001D: ldc.i4.5 IL_001E: call System.Nullable..ctor IL_0023: ret 

Nós vemos que o compilador muda int? a = null int? a = null para algo como int? a = new int?() int? a = new int?() que é bem diferente do object a3 = null . Então, claramente, os Nullables têm um tratamento especial de compilador.