Inferencia de tipo genérico C # 3.0 – pasar un delegado como parámetro de función

Me pregunto por qué el comstackdor 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(Action f) { } static void test() { Action 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 comstackdor infiera el tipo de Action de la firma de la función que se pasa, pero esto no funciona. Sin embargo, puedo crear una Action desde foo sin conversión, ¿existe una razón legítima para que el comstackdor no pueda hacer lo mismo a través de la inferencia de tipo?

Tal vez esto lo hará más claro:

 public class SomeClass { static void foo(int x) { } static void foo(string s) { } static void bar(Action f){} static void barz(Action f) { } static void test() { Action f = foo; bar(f); barz(foo); bar(foo); //these help the compiler to know which types to use bar(foo); bar( (int i) => foo(i)); } } 

foo no es una acción – foo es un grupo de métodos.

  • En la statement de asignación, el comstackdor puede decir claramente de qué fuente está hablando, ya que se especifica el tipo int.
  • En la instrucción barz (foo), el comstackdor 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 comstackdor se da por vencido.

Editar: he agregado dos (más) formas para ayudar al comstackdor 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 x) { } static void bar(Action 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.

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 sino solo a un delegado específico del tipo como Action .

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.

Tenga en cuenta que la tarea

 Action f = foo; 

ya tiene mucha azúcar sintáctica. El comstackdor en realidad genera código para esta statement:

 Action f = new Action(foo); 

La llamada al método correspondiente comstack sin problema:

 bar(new Action(foo)); 

Fwiw, también ayuda al comstackdor a deducir el argumento de tipo:

 bar(foo); 

Entonces, todo se reduce a la pregunta: ¿por qué el azúcar en la statement 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 comstackdor 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.

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.