Bueno, no sé sobre el caso SqlTypes
, pero definitivamente hay algunas razones técnicas por las que la adición de una conversión implícita entre DBNull.Value
y los valores de Nullable<T>
con HasValue = false
no funcionarían.
Recuerde, DBNull
es un tipo de referencia, ya pesar del hecho de que los actos Nullable<T>
como un tipo de referencia - pretendiendo ser capaz de asumir el valor null
- en realidad es un tipo de valor, la semántica de valor.
En particular, hay un caso de borde raro cuando los valores del tipo Nullable<T>
están en caja. El comportamiento se encapsula de forma especial en los valores de tiempo de ejecución a box del tipo Nullable<T>
en una versión en caja de T
, no en una versión en caja de Nullable<T>
.
Como the MSDN documentation lo explica:
Cuando se encajona un tipo anulable, el tiempo de ejecución de lenguaje común cajas automáticamente el valor subyacente de la anulable (Of T) objeto, no el objeto anulable (Of T) en sí. Es decir, si la propiedad HasValue es verdadera, el contenido de la propiedad Value está enmarcado. Cuando el valor subyacente de un tipo que admite nulos es unboxed, el tiempo de ejecución de lenguaje común crea una nueva estructura Nullable (Of T) inicializada al valor subyacente.
Si la propiedad HasValue de un tipo que admite nulos es falsa, el resultado de una operación de boxeo es Nothing. En consecuencia, si un tipo anulado en caja se pasa a un método que espera un argumento de objeto, ese método debe estar preparado para manejar el caso donde el argumento es Nothing. Cuando Nothing se desempaqueta en un tipo anulable, el tiempo de ejecución de lenguaje común crea una nueva estructura Nullable (Of T) e inicializa su propiedad HasValue a false.
Ahora entramos en un problema difícil: la especificación del lenguaje C# (§ 4.3.2) dice que no podemos utilizar una conversión unboxing para convertir DBNull.Value
en Nullable<T>
:
Para una conversión unboxing a un dado anulable de tipo para tener éxito en tiempo de ejecución, el valor del operando fuente debe ser nula o una referencia a un valor en caja del subyacente de tipo no anulable-valor de la anulable de tipo .Si el operando fuente es una referencia a un objeto incompatible, se lanza un System.InvalidCastException
.
Y no podemos utilizar una conversión definida por el usuario convertir de object
a Nullable<T>
, ya sea, según § 10.10.3:
No es posible redefinir directamente un pre-definido conversión. Por lo tanto, los operadores de conversión no pueden convertir de object
porque ya existen conversiones implícitas y explícitas entre object
y todos los demás tipos.
OK, usted o yo no podría hacerlo, pero Microsoft podría simplemente modificar la especificación, y que sea legal, ¿verdad? No lo creo.
¿Por qué? Bueno, imagine el caso de uso previsto: tiene algún método que se especifica para devolver object
. En la práctica, devuelve DBNull.Value
o int
. Pero, ¿cómo podría el compilador saber eso? Todo lo que sabe es que el método se especifica para devolver object
. Y el operador de conversión que se aplicará debe seleccionarse en tiempo de compilación.
OK, así que supongamos que hay algún operador mágico que puede convertir de object
a Nullable<T>
, y el compilador tiene alguna forma de saber cuándo es aplicable. (No queremos que se use para cada método que se especifica para devolver object
- ¿qué debería hacer si el método realmente devuelve un string
?) Pero todavía tenemos un problema: ¡la conversión podría ser ambigua! Si el método devuelve long
o DBNull.Value
, y hacemos int? v = Method();
, ¿qué deberíamos hacer cuando el método devuelve un long
enmarcado?
Básicamente, para que esto funcione según lo previsto, tendría que usar el equivalente de dynamic
para determinar el tipo en tiempo de ejecución y convertir en función del tipo de tiempo de ejecución. Pero luego hemos roto otra regla: dado que la conversión real solo se seleccionaría en el tiempo de ejecución, no hay garantía de que realmente tenga éxito. Pero conversiones implícitas no deben arrojar excepciones.
Por lo tanto, en este punto, no se trata solo de un cambio en el comportamiento específico del lenguaje, un golpe de rendimiento potencialmente significativo, y además podría generar una excepción inesperada. Parece una buena razón para no implementarlo. Pero si necesita una razón más, recuerde que cada función comienza en minus 100 points.
En resumen: lo que realmente desea aquí no se puede hacer con una conversión implícita de todos modos. Si desea el comportamiento de dynamic
, simplemente use dynamic
! Esto hace lo que quiere, y ya está implementado en C# 4.0:
object x = 23;
object y = null;
dynamic dx = x;
dynamic dy = y;
int? nx = (int?) dx;
int? ny = (int?) dy;
Console.WriteLine("nx.HasValue = {0}; nx.Value = {1}; ny.HasValue = {2};",
nx.HasValue, nx.Value, ny.HasValue);
@DanielPryden: Ok, por lo que estaría unboxing en un valor diferente dependiendo de si el objetivo era un tipo de valor o referencia? ¿Es eso lo que quieres decir? – jmoreno
@DanielPryden: La pregunta ha sido reabierta, espero que esta vez permanezca abierta el tiempo suficiente para que usted ingrese una respuesta. – jmoreno
OK, he reescrito mi respuesta, esta vez con referencias a la especificación. Resulta que en realidad es peor de lo que pensaba: en este punto, estoy bastante convencido de que no es solo una mala idea, es completamente imposible. ¡Afortunadamente mi respuesta valió la pena la espera! –