2012-01-10 12 views
6

¿Existe alguna razón técnica por la cual no haya una conversión implícita de DBNull a los diversos tipos de nullable y/o sql? Entiendo por qué las conversiones no ocurren actualmente, pero no entiendo por qué una conversión implícita no se creó en ese momento o se agregó en las versiones posteriores del marco.¿Existe alguna razón técnica para no realizar una conversión implícita de DBNull a tipos anulables?

Para que quede claro, estoy buscando razones técnicas, no "porque así es como lo hicieron" o "Me gusta de esa manera".

+0

@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

+0

@DanielPryden: La pregunta ha sido reabierta, espero que esta vez permanezca abierta el tiempo suficiente para que usted ingrese una respuesta. – jmoreno

+0

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! –

Respuesta

3

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); 
+1

Sí, valió la pena esperar. Una pequeña advertencia, el operador coalescente C# nulo en realidad hace la conversión bastante simple (a través de un mecanismo diferente, por lo que estaba preguntando sobre el marco). ¿En t? i = x como int? ?? nulo; Esto es específico para el compilador C# y el ?? operador, y si bien cualquier idioma podría, por supuesto, agregar el equivalente, no es inherente a la CLR (que es lo que me interesa). – jmoreno

+0

@jmoreno: Hmm ... ese es un truco inteligente con los operadores 'as' y' ?? ', no había pensado en eso. Probablemente sea mucho más eficiente que 'dinámico ', aunque todavía no creo que puedas hacerlo como operador de conversión * implícito *, ya que necesitarás saber el tipo de valor apropiado. Probablemente puedas hacerlo con un operador de conversión * explicit *, aunque es posible que quieras probar un método de extensión, que podría ser más fácil de usar. –

+0

bien, tampoco creo que puedas hacerlo con un operador implícito. En C# puede escribir un método de extensión, no puede hacerlo en VB, ya que no le permite hacerlo para Object debido a posibles conflictos con el enlace tardío. Tengo una función de utilidad genérica para VB, que funciona. – jmoreno

7

La explicación más sencilla proviene de Microsoft's documentation:

CLR tipos anulables no están destinados para el almacenamiento de valores nulos de base de datos debido a una nula ANSI SQL no se comporta de la misma manera que una referencia nula (o nada en Visual BASIC).

+5

En otras palabras, intencionalmente no hicimos esto porque queremos que esté completamente enterado de lo que está haciendo. – NotMe

+0

@competent_tech: la siguiente frase de allí es "Para trabajar con valores nulos de ANSI SQL de base de datos, use System.Data.SqlTypes nulls en lugar de Nullable.", Pero ... "El valor del tipo 'System.DBNull' no se puede convertir a 'System.Data.SqlTypes.SqlInt32' " – jmoreno

+0

@jmoreno: Creo que esto se aborda un poco más adelante en la asignación de valores nulos:' Los nulos en System.Data.SqlTypes son específicos del tipo y no se pueden representar con un solo valor , como DbNull. Utilice la propiedad IsNull para comprobar nulls.' –

Cuestiones relacionadas