2011-10-31 11 views
5

Expression.Convert generalmente arroja InvalidOperationException cuando "No se define operador de conversión entre expression.Type y type".Expression.Convert no arroja InvalidOperationException para los parámetros de tipo de valor invariable?

El parámetro de tipo de retorno de Func<> es covariante para los tipos de referencia.

// This works. 
Func<SomeType> a =() => new SomeType(); 
Func<object> b = a; 

Es isn't covariant for value types.

La varianza solo se aplica a los tipos de referencia; si especifica un tipo de valor para un parámetro de tipo de variante, ese parámetro de tipo es invariante para el tipo construido resultante.

// This doesn't work! 
Func<int> five =() => 5; 
Func<object> fiveCovariant = five; 

Sin embargo, Expression.Convert cree que es posible.

Func<int> answer =() => 42; 
Expression answerExpression = Expression.Constant(answer); 
// No InvalidOperationException is thrown at this line. 
Expression converted 
    = Expression.Convert(answerExpression, typeof(Func<object>)); 

Sin InvalidOperationException se lanza al llamar Expression.Convert. El árbol de expresiones se compila correctamente, pero cuando llamo al delegado creado, obtengo un InvalidCastException esperado.

  1. ¿Esto es un error? (I reported it as a bug on Microsoft Connect.)
  2. ¿Cómo comprobar correctamente si un tipo se puede convertir a otro tipo? Some answers parecen referirse al uso de Convert. Preferiría mucho un método que no tenga que usar el manejo de excepciones como lógica.

Parece toda la lógica de la varianza no es soportado adecuadamente. Se queja correctamente de no ser capaz de convertir de Func<SomeType> a Func<SomeOtherType>, pero no se queja de la conversión de Func<object> a Func<string>.

Curiosamente, una vez SomeType y SomeOtherType están en la misma jerarquía de clases (SomeOtherType se extiende desde SomeType), nunca se produce la excepción. Si no lo son, lo hace.

Respuesta

7

Es esto un error?

Sí. La biblioteca del árbol de expresiones probablemente no se actualizó de manera consistente cuando agregamos covarianza y contravariancia. Lo siento por eso.

Lo indiqué como un error en Microsoft Connect.

Gracias! Alguien lo mirará entonces.

Cómo comprobar correctamente si un tipo se puede convertir en otro tipo?

La pregunta es vaga. Dado dos objetos tipo, ¿quiere saber:

  • ¿El tiempo de ejecución de .NET cree que los tipos son compatibles con la asignación?
  • ¿El compilador de C# piensan que hay una conversión implícita entre los tipos?
  • ¿El compilador de C# cree que hay una conversión explícita entre los tipos?

"int" y "short", por ejemplo, no son compatibles con la asignación por reglas de .NET. Int es explícitamente convertible pero no implícitamente convertible a corto, y corto es implícita y explícitamente convertible a int, por reglas de C#.

+1

_ "Dado dos objetos de tipo, ¿desea saber" _: como en la [documentación de msdn en Expression.Convert] (http://msdn.microsoft.com/en-us/library/bb292051.aspx): _ "Ningún operador de conversión se define entre expression.Type y type." _ En este momento no tengo forma de saber si mi árbol de expresiones representa código válido, hasta ejecutarlo. Para mis propósitos, necesito saber si una conversión implícita es posible. –

1

No es un error. Expression.Convert representa una comprobación de tipo en tiempo de ejecución, por lo que el comportamiento esperado sería una InvalidCastException en tiempo de ejecución.

Editar: eso no es del todo correcto. No representa exactamente una verificación de tipo de tiempo de ejecución (Aquí está la documentación: http://msdn.microsoft.com/en-us/library/bb292051.aspx). Sin embargo, el árbol de expresiones se crea en tiempo de ejecución, por lo que todo el tipo de verificación debe ocurrir en ese momento.

Editar: Estoy usando .NET 4.0 también.

Por cierto, Convert no se queja acerca de la conversión Func<object>-Func<string>debido a que la conversión es a veces legal. Es legal si el Func<object> es una referencia covariante a un objeto cuyo tipo de tiempo de ejecución es Func<string>.Ejemplo:

Func<string> sFunc =() => "S"; 
Func<object> oFunc = sFunc; 
Func<string> anotherSFunc = (Func<string>)oFunc; 

Ahora, convertir decide si va a lanzar una excepción InvalidOperationException comprobando si un tipo se puede forzar a otro. Al verificar delegados para una posible conversión de referencia, parece que el código verifica los parámetros contravariantes (argumento) y arroja un InvalidOperationException si alguno es un tipo de valor. No parece hacer esa comprobación para el parámetro covariante (tipo de retorno). Así que estoy empezando a sospechar que este es un error, aunque estoy inclinado a reservar el juicio en que hasta que tenga la oportunidad de mirar a la especificación (véase Eric Lippert de Maybe there's something wrong with the universe, but probably not), que no tengo tiempo para hacer ahora mismo.

+0

Exactamente, entonces sabría que no puede convertir 'Func ' a 'Func '? P.ej. sabe que no puede convertir 'Func ' a 'Func '. EDITAR: Parece que tampoco lo sabe. : O –

+0

Quien votó en contra, explique. – phoog

+0

@StevenJeuris Exactamente. El punto es que cualquier cosa que Convert conozca, solo puede saberlo en tiempo de ejecución. El compilador no hace el análisis estático. Entonces puede llamar a Convert con todo tipo de conversiones ilegales, y las llamadas se compilarán. No fallarán hasta que se ejecute el código. – phoog

0

Eric Lippert respondieron parte 1 de mi pregunta: parece ser un error. Empecé a buscar una solución a la pregunta 2:

¿Cómo comprobar correctamente si un tipo se puede convertir a otro tipo?

Acabo de cometer un primer intento en un Type.CanConvertTo(Type to) método to my library. (. Las fuentes son demasiado complejas para publicar aquí, lo siento)

if (fromType.CanConvertTo(toType)) 
{ 
    convertedExpression = Expression.Convert(expression, toType); 
} 

Hasta ahora soporta la comprobación de conversiones implícitas para:

  • tipos no genéricos simples.
  • Varianza para interfaces genéricas anidadas y delegados.
  • Invarianza para los parámetros del tipo de valor genérico.

No es compatible con:

  • restricciones de tipo.
  • Operadores de conversión implícita personalizados.

Pasa all my tests for implicit conversions. Aunque también puede especificar incluir conversiones explícitas (y de hecho use it like that at the moment), aún necesito escribir pruebas de unidad para todos esos escenarios.

Cuestiones relacionadas