c# - matriz - ¿Por qué no puede el compilador decir el mejor destino de conversión en este caso de resolución de sobrecarga?(covarianza)
covarianza y correlacion (2)
Comprender la especificación del lenguaje C # sobre la resolución de sobrecarga es claramente difícil, y ahora me pregunto por qué este simple caso falla:
void Method(Func<string> f)
{
}
void Method(Func<object> f)
{
}
void Call()
{
Method(() => { throw new NotSupportedException(); });
}
Esto da el error CS0121 en tiempo de compilación. La llamada es ambigua entre los siguientes métodos o propiedades: seguido por mis dos miembros de la función Method
(sobrecargas).
Lo que habría esperado era que Func<string>
fuera un mejor objetivo de conversión que Func<object>
, y que luego se utilizara la primera sobrecarga.
Desde .NET 4 y C # 4 (2010), el tipo de delegado genérico Func<out TResult>
ha sido covariante en TResult
, y por esa razón existe una conversión implícita de Func<string>
a Func<object>
mientras que claramente ninguna conversión implícita puede existen desde Func<object>
a Func<string>
. Entonces, ¿ Func<string>
sería el mejor objetivo de conversión, y la resolución de sobrecarga debería elegir la primera sobrecarga?
Mi pregunta es simple: ¿Qué parte de la Especificación de C # me falta aquí?
Además: Esto funciona bien:
void Call()
{
Method(null); // OK!
}
Bueno, tienes razón. Lo que causa el problema aquí es el delegado que está pasando como argumento. No tiene un tipo de devolución explícito, solo está lanzando una excepción. Exception
es básicamente un object
pero no se considera un tipo de devolución de un método. Como no hay devolución de llamada después del lanzamiento de excepción, el compilador no está seguro de qué sobrecarga debería usar.
Solo prueba esto
void Call()
{
Method(() =>
{
throw new NotSupportedException();
return "";
});
}
No hay problema con elegir una sobrecarga ahora debido al tipo explícitamente declarado de un objeto pasado a una devolución. No importa que la llamada de devolución sea inalcanzable debido al lanzamiento de excepción, pero ahora el compilador sabe qué sobrecarga debería usar.
EDITAR:
En cuanto al caso de pasar nulo, frenkly, no sé la respuesta.
Mi pregunta es simple: ¿Qué parte de la Especificación de C # me falta aquí?
Resumen:
- Has encontrado un error conocido menor en la implementación.
- El error se conservará por razones de compatibilidad con versiones anteriores.
- La especificación C # 3 contenía un error con respecto a cómo debía manejarse el caso "nulo"; fue corregido en la especificación C # 4.
- Puede reproducir el comportamiento del buggy con cualquier lambda donde no se pueda inferir el tipo de retorno. Por ejemplo:
Method(() => null);
Detalles:
La especificación C # 5 dice que la regla de la mejora es:
Si la expresión tiene un tipo, elija la mejor conversión de ese tipo a los tipos de parámetros candidatos.
Si la expresión no tiene un tipo y no es un lambda, elija la conversión al tipo que sea mejor.
Si la expresión es una lambda, primero considere qué tipo de parámetro es mejor; si ninguno es mejor y los tipos de delegado tienen listas de parámetros idénticas, entonces considere la relación entre el tipo de retorno inferido de la lambda y los tipos de retorno de los delegados.
Entonces, el comportamiento previsto es: primero, el compilador debe verificar si un tipo de parámetro es claramente mejor que el otro, independientemente de si el argumento tiene un tipo. Si eso no resuelve la situación y el argumento es una lambda, entonces verifique cuál de los tipos de retorno inferidos convertidos a los parámetros tipo de retorno ''tipos de delegado'' es mejor.
El error en la implementación es que la implementación no hace eso . Por el contrario, en el caso en que el argumento es un lambda omite por completo el control de calidad del tipo y va directamente a la verificación de la mejora del tipo de devolución inferida, que luego falla porque no hay un tipo de devolución inferido.
Mi intención era arreglar esto para Roslyn. Sin embargo, cuando fui a implementar esto, descubrimos que hacer la corrección causaba que el código del mundo real dejara de compilar. (No recuerdo cuál era el código del mundo real y ya no tengo acceso a la base de datos que contiene los problemas de compatibilidad.) Por lo tanto, decidimos mantener el pequeño error existente.
Noté que el error era básicamente imposible antes de agregar la variación de delegado en C # 4; en C # 3 era imposible que dos tipos diferentes de delegados fueran más o menos específicos, por lo que la única regla que podía aplicar era la regla lambda. Como no había ninguna prueba en C # 3 que revelara el error, era fácil de escribir. Mi mal, lo siento.
También observo que cuando comienzas a lanzar tipos de árbol de expresiones en la mezcla, el análisis se vuelve aún más complicado. Aunque Func<string>
es mejor que Func<object>
, Expression<Func<string>>
no es convertible a Expression<Func<object>>
! Sería bueno si el algoritmo de la mejoría fuera agnóstico con respecto a si la lambda iba a un árbol de expresiones o un delegado, pero de alguna manera no lo es. Esos casos se complican y no quiero trabajar el punto aquí.
Este error menor es una lección objetiva sobre la importancia de implementar lo que dice la especificación y no lo que piensas que dice . Si hubiera tenido más cuidado en C # 3 para asegurarme de que el código coincidía con la especificación, entonces el código habría fallado en el caso "nulo" y habría quedado claro antes que la especificación C # 3 era incorrecta. Y la implementación hace el chequeo lambda antes de la verificación de tipo, que era una bomba de tiempo que esperaba estallar cuando C # 4 rodó y de repente se convirtió en código incorrecto. La verificación de tipo debería haberse hecho primero independientemente.