Pregunta

Me pregunto por qué el compilador de C# 3.0 no puede inferir el tipo de un método cuando se pasa como parámetro a una función genérica cuando implícitamente puede crear 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
    }   
}

Pensé 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 un Action<int> de foo sin conversión, ¿hay alguna razón legítima por la que el compilador no pueda hacer lo mismo mediante inferencia de tipos?

¿Fue útil?

Solución

Quizás esto lo aclare:

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é tonto estás hablando, ya que se especifica el tipo int.
  • En la instrucción barz (foo), el compilador puede decir de qué foo estás 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 de ayudar al compilador a determinar 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 basarse 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?
  }

Dado que el problema general no se podía resolver, eligen dejar problemas específicos donde existe una solución como no resuelta.

Como consecuencia de esta decisión, no agregará una sobrecarga para un método y obtendrá una gran cantidad de confusión de todas las personas que llaman que están acostumbradas a un solo grupo de métodos miembro. Supongo que es algo bueno.

Otros consejos

El razonamiento es que si el tipo alguna vez se expande, no debería haber posibilidad de falla. es decir, si se agrega un método foo (cadena) 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 (conocida como grupo de métodos) no se puede enviar a un delegado no específico del tipo, como Action<T> sino solo a un tipo- delegado específico como Action<int>.

Eso es un poco extraño, sí. La especificación C # 3.0 para la inferencia de tipos 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 debería mencionar los grupos de métodos en la primera viñeta (ya que no están cubiertos por la inferencia explícita de tipo de parámetro (7.4.2.7), lo que significa que debe usar inferencia de tipo de salida (7.4.2.6). parece que debería funcionar, pero obviamente no :(

Sé que MS está buscando mejorar la especificación 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 tener un caso especial cuando el grupo de métodos es en realidad un solo método, es cierto.

Eric Lippert tiene una entrada de blog en la inferencia de tipos de retorno no funciona con grupos de métodos que es similar en este caso, pero aquí no estamos interesado en el tipo de retorno, solo en el tipo de parámetro. Es posible que otras publicaciones en su serie de inferencia de tipos aunque puede ayudar.

Tenga en cuenta que la tarea

Action<int> f = foo;

ya tiene mucho azúcar sintáctico. El compilador realmente genera código para esta declaración:

Action<int> f = new Action<int>(foo);

La llamada al método correspondiente se compila sin problemas:

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 tuvieron que lidiar con el problema de resolución de sobrecarga. Las reglas de las cuales son bastante elaboradas. Probablemente simplemente no lo lograron.

Solo para completar, esto no es específico de C #: el mismo código 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: no se puede inferir el parámetro de tipo 'T' para 'Public Subbar (Of T) (f As System.Action (Of T))'.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top