valor - operador ? c#
¿Cuál es la justificación para este comportamiento Nullable<T> con operadores de conversión implícita? (1)
Dije anteriormente que (1) este es un error del compilador y (2) es uno nuevo. La primera declaración fue precisa; el segundo fue que me confundí en mi prisa por llegar al autobús a tiempo. (El error en el que estaba pensando que es nuevo para mí es un error mucho más complicado que implica conversiones elevadas y operadores de incremento elevado).
Este es un error de compilación conocido de larga data. Jon Skeet me llamó la atención por primera vez hace un tiempo y creo que hay una pregunta de StackOverflow sobre eso en algún lugar; No recuerdo dónde de la mano. Quizás Jon lo haga.
Entonces, el error. Vamos a definir un operador "levantado". Si un operador convierte de un tipo de valor no anulable S a un tipo de valor no anulable T, entonces también hay un operador "levantado" que se convierte de S? ¿a T ?, tal que un S nulo? se convierte a un T nulo? y un S no nulo? se convierte a T? al desenvolver S? a S, convirtiendo S en T, y envolviendo T en T?
La especificación dice que (1) la única situación en la que hay un operador elevado es cuando S y T son tipos de valores no anulables, y (2) que los operadores de conversión levantados y no levantados se consideran si son candidatos aplicables para la conversión y, si ambos son aplicables, entonces los tipos de origen y destino de las conversiones aplicables, elevadas o no elevadas, se utilizan para determinar el mejor tipo de fuente, el mejor tipo de destino y, en última instancia, la mejor conversión de todas las conversiones aplicables.
Desafortunadamente, la implementación viola todas estas reglas y lo hace de una manera que no podemos cambiar sin romper muchos programas existentes.
En primer lugar, violamos la regla sobre la existencia de operadores elevados. La implementación considera que un operador elevado existe si S y T son tipos de valores no anulables, o si S es un tipo de valor no anulable y T es cualquier tipo al que se pueda asignar un nulo: tipo de referencia, valor anulable tipo, o tipo puntero. En todos esos casos, producimos un operador elevado.
En su caso particular, elevamos a anulable diciendo que convertimos un tipo anulable al tipo de referencia Cat comprobando si es nulo. Si la fuente no es nula entonces convertimos normalmente; si es así, entonces producimos un Cat nulo.
En segundo lugar, violamos a fondo la regla sobre cómo determinar los mejores tipos de origen y destino de los candidatos aplicables cuando uno de esos candidatos es un operador elevado, y también violamos las reglas sobre la determinación de cuál es el mejor operador.
En resumen, es un gran lío que no se puede arreglar sin romper clientes reales, por lo que probablemente consagraremos el comportamiento en Roslyn. Consideraré documentar el comportamiento exacto del compilador en mi blog en algún momento, pero no aguanto la respiración mientras espero ese día si fuera usted.
Y, por supuesto, muchas disculpas por los errores.
Encontré un comportamiento interesante en la interacción entre las conversiones Nullable
y las implícitas. Descubrí que al proporcionar una conversión implícita para un tipo de referencia a partir de un tipo de valor, permite que el tipo de Nullable
se pase a una función que requiere el tipo de referencia cuando en cambio espero un error de compilación. El siguiente código demuestra esto:
static void Main(string[] args)
{
PrintCatAge(new Cat(13));
PrintCatAge(12);
int? cat = null;
PrintCatAge(cat);
}
private static void PrintCatAge(Cat cat)
{
if (cat == null)
System.Console.WriteLine("What cat?");
else
System.Console.WriteLine("The cat''s age is {0} years", cat.Age);
}
class Cat
{
public int Age { get; set; }
public Cat(int age)
{
Age = age;
}
public static implicit operator Cat(int i)
{
System.Console.WriteLine("Implicit conversion from " + i);
return new Cat(i);
}
}
Salida:
The cat''s age is 13 years
Implicit conversion from 12
The cat''s age is 12 years
What cat?
Si el código de conversión se elimina de Cat
entonces obtienes los errores esperados:
Error 3 The best overloaded method match for ''ConsoleApplication2.Program.PrintCatAge(ConsoleApplication2.Program.Cat)'' has some invalid arguments
Error 4 Argument 1: cannot convert from ''int?'' to ''ConsoleApplication2.Program.Cat
Si abre el ejecutable con ILSpy, el código que se generó es el siguiente
int? num = null;
Program.PrintCatAge(num.HasValue ? num.GetValueOrDefault() : null);
En un experimento similar, PrintCatAge
la conversión y agregué una sobrecarga a PrintCatAge
que toma un int (no anulable) para ver si el compilador realizaría una operación similar, pero no lo hace.
Entiendo lo que está sucediendo, pero no entiendo la justificación de ello. Este comportamiento es inesperado para mí y parece extraño. No tuve éxito en encontrar ninguna referencia a este comportamiento en MSDN en la documentación para conversiones o Nullable<T>
.
La pregunta que formulo entonces es: ¿es esto intencional y hay una explicación de por qué sucede esto?