puede - Inferencia de tipo genérico C#3.0-pasar un delegado como parámetro de función
no se puede convertir expresion lambda en el tipo delegado (5)
El razonamiento es que si el tipo alguna vez se expande, no debería haber posibilidad de falla. es decir, si un método foo (cadena) se agrega al tipo, nunca debería importar al código existente, siempre y cuando el contenido de los métodos existentes no cambie.
Por esa razón, incluso cuando solo hay un método foo, una referencia a foo (conocido como grupo de métodos) no se puede convertir a un delegado no específico del tipo, como Action<T>
sino solo a un delegado específico del tipo como Action<int>
.
Me pregunto por qué el compilador C # 3.0 no puede inferir el tipo de un método cuando se pasa como un parámetro a una función genérica cuando puede crear implícitamente un delegado para el mismo método.
Aquí hay un ejemplo:
class Test
{
static void foo(int x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
Action<int> f = foo; // I can do this
bar(f); // and then do this
bar(foo); // but this does not work
}
}
Hubiera pensado que podría pasar foo
a bar
y hacer que el compilador infiera el tipo de Action<T>
de la firma de la función que se pasa, pero esto no funciona. Sin embargo, puedo crear una Action<int>
desde foo
sin conversión, ¿existe una razón legítima para que el compilador no pueda hacer lo mismo a través de la inferencia de tipo?
Eso es un poco extraño, sí. La especificación de C # 3.0 para la inferencia de tipo es difícil de leer y tiene errores, pero parece que debería funcionar. En la primera fase (sección 7.4.2.1) creo que hay un error: no debe mencionar los grupos de métodos en la primera viñeta (ya que no están cubiertos por la inferencia de tipo de parámetro explícito (7.4.2.7), lo que significa que debería usar Inferencia del tipo de salida (7.4.2.6). Parece que debería funcionar, pero obviamente no :(
Sé que MS está buscando mejorar las especificaciones para la inferencia de tipos, por lo que podría ser un poco más claro. También sé que, independientemente de la dificultad de leerlo, existen restricciones en los grupos de métodos y en la inferencia de tipos, restricciones que podrían ser especiales cuando el grupo de métodos es solo un método único, hay que admitirlo.
Eric Lippert tiene una entrada de blog sobre inferencia de tipo de retorno que no funciona con grupos de métodos, que es similar a este caso, pero aquí no estamos interesados en el tipo de devolución, solo en el tipo de parámetro. Sin embargo, es posible que otras publicaciones en su serie de inferencias de tipo puedan ayudar.
Solo para completar, esto no es específico de C #: el mismo código de VB.NET falla de manera similar:
Imports System
Module Test
Sub foo(ByVal x As integer)
End Sub
Sub bar(Of T)(ByVal f As Action(Of T))
End Sub
Sub Main()
Dim f As Action(Of integer) = AddressOf foo '' I can do this
bar(f) '' and then do this
bar(AddressOf foo) '' but this does not work
End Sub
End Module
error BC32050: El parámetro de tipo ''T'' para ''Subbarra pública (De T) (f Como System.Action (Of T))'' no se puede inferir.
Tal vez esto lo hará más claro:
public class SomeClass
{
static void foo(int x) { }
static void foo(string s) { }
static void bar<T>(Action<T> f){}
static void barz(Action<int> f) { }
static void test()
{
Action<int> f = foo;
bar(f);
barz(foo);
bar(foo);
//these help the compiler to know which types to use
bar<int>(foo);
bar( (int i) => foo(i));
}
}
foo no es una acción - foo es un grupo de métodos.
- En la declaración de asignación, el compilador puede decir claramente de qué fuente está hablando, ya que se especifica el tipo int.
- En la instrucción barz (foo), el compilador puede decir de qué fuente está hablando, ya que se especifica el tipo int.
- En la instrucción bar (foo), podría ser cualquier foo con un solo parámetro, por lo que el compilador se da por vencido.
Editar: he agregado dos (más) formas para ayudar al compilador a descifrar el tipo (es decir, cómo omitir los pasos de inferencia).
De mi lectura del artículo en la respuesta de JSkeet, la decisión de no inferir el tipo parece estar basada en un escenario de inferir mutuamente, como
static void foo<T>(T x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
bar(foo); //wut''s T?
}
Como el problema general no se resolvió, eligen dejar problemas específicos donde existe una solución sin resolver.
Como consecuencia de esta decisión, no agregará una sobrecarga para un método y obtendrá mucha confusión de tipo de todas las personas que llaman que se utilizan para un grupo de métodos de un solo miembro. Supongo que eso es algo bueno.
Tenga en cuenta que la tarea
Action<int> f = foo;
ya tiene mucha azúcar sintáctica. El compilador en realidad genera código para esta declaración:
Action<int> f = new Action<int>(foo);
La llamada al método correspondiente compila sin problema:
bar(new Action<int>(foo));
Fwiw, también ayuda al compilador a deducir el argumento de tipo:
bar<int>(foo);
Entonces, todo se reduce a la pregunta: ¿por qué el azúcar en la declaración de asignación pero no en la llamada al método? Tendría que adivinar que es porque el azúcar no es ambiguo en la tarea, solo hay una posible sustitución. Pero en el caso de las llamadas a métodos, los escritores del compilador ya tenían que lidiar con el problema de resolución de sobrecarga. Las reglas de los cuales son bastante elaboradas. Probablemente no lo entendieron.