2010-01-21 20 views
12

Supongamos que tengo.NET: inferidos tipos genéricos sobre los métodos estáticos

public static List<T2> Map<T,T2>(List<T> inputs, Func<T, T2> f) 
{ 
    return inputs.ConvertAll((x) => f(x)); 
} 

private int Square(int x) { return x*x; } 

public void Run() 
{ 
    var inputs = new List<Int32>(new int[]{2,4,8,16,32,64,128,256,512,1024,2048}); 

    // this does not compile 
    var outputs = Map(inputs, Square); 

    // this is fine 
    var outputs2 = Map<Int32,Int32>(inputs, Square); 

    // this is also fine (thanks, Jason) 
    var outputs2 = Map<Int32,Int32>(inputs, (x)=>x*x); 

    // also fine 
    var outputs2 = Map(inputs, (x)=>x*x); 
} 

¿Por qué no compilar?

EDITAR: El error es: CS0411

error: Los argumentos de tipo para el método de 'Namespace.Map < T, T2 > (System.Collections.Generic.List <T>, System.Func < T, T2 >) 'no se puede inferir del uso. Intente especificar los argumentos de tipo explícitamente.

¿Por qué tengo que especificar el tipo de la función Map()? ¿No puede inferirse esto del Func<T> pasado? (En mi caso, Plaza)


¿Es la respuesta el mismo que para
C# 3.0 generic type inference - passing a delegate as a function parameter?

+0

Interesante. ¿Le importaría publicar el error del compilador? –

+0

¿Cuál es el resultado si cambia la var en la primera línea de Ejecutar() en la Lista ? –

+0

@tehMick: El resultado será el mismo. El tipo se infiere. –

Respuesta

8

Desde su mensaje de error:

El tipo de argumentos para el método '[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)' no se pueden deducir a partir del uso. Intente especificar los argumentos de tipo explícitamente.

Tenga en cuenta que el mensaje de error indica que no puede encontrar los argumentos de tipo. Es decir, está teniendo problemas para resolver uno de los parámetros de tipo T o T2. Esto se debe a §25.6.4 (Inferencia de argumentos de tipo) de la especificación. Esta es la parte de la especificación que trata de inferir parámetros genéricos de tipo.

Nada se infiere a partir del argumento (pero la inferencia de tipos tiene éxito) si cualquiera de las siguientes son verdaderas:

[...]

El argumento es un grupo método.

Por lo tanto, el compilador no es capaz de utilizar el tipo de delegado de Square para inferir el tipo de T2. Tenga en cuenta que si cambia la declaración de

public static List<T> Map<T>(List<T> inputs, Func<T, T> f) { 
     return inputs.ConvertAll((x) => f(x)); 
} 

continuación

var outputs = Map(inputs, Square); 

es legal. En este caso, ya se ha resuelto que T es int por el hecho de que inputs es List<int>.

Ahora, la pregunta más profunda es por qué es la anterior la especificación? Es decir, ¿por qué los grupos de métodos no desempeñan un papel en la resolución de parámetros de tipo? Creo que es debido a casos como este:

class Program { 
    public static T M<T>(Func<T, T> f) { 
     return default(T); 
    } 

    public static int F(int i) { 
     return i; 
    } 

    public static float F(float f) { 
     return f; 
    } 

    static void Main(string[] args) { 
     M(F); // which F am I? 
    } 
} 
+0

Solo para la validación múltiple, IanG de este MSDN C# Forum parece decir lo mismo y entra en un poco de el Por qué la especificación lo dice: http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/a4847737-4a6b-4fcd-89f2-1b213aaf8422 Me pareció un tanto esclarecedor también. –

+0

@jdk: Hmm ... ver mi edición. – jason

+0

'Mapa (entradas de la lista , Func f)'? No hay mucho de una función de mapa si la entrada y la salida son del mismo tipo, ¿eh? ;) – Juliet

2

La inferencia falla en inferir el tipo del delegado, no la lista:

// this is also fine 
var outputs3 = Map(inputs, new Func<int, int>(Square)); 

// more calls that compile correctly 
var outputs4 = Map(inputs, x => Square(x)); 

var outputs5 = Map(inputs, x => x * x); 

Func<int, int> t = Square; 
var outputs6 = Map(inputs, t); 

No sé por qué, sin embargo - tal vez simplemente no hay encasillado implícita a partir de la firma del Square-Func<Int32, Int32>? Parece extraño que Func<int, int> t = Square; sea válido, pero el compilador no puede dar el salto por sí mismo ... ¿Error, tal vez?

+0

Lea el mensaje de error. Los argumentos de tipo para el método '' [...]. Map (System.Collections.Generic.List , System.Func ) ''. No puede descifrar cuáles son los parámetros de tipo genérico. Puede resolver 'T' porque' inputs' es una 'List ' por lo que sabe que 'T' es' int'. Por lo tanto, no puede resolver 'T2'. Ver mi respuesta – jason

+0

No entiendo cómo esta respuesta tiene votos ascendentes; está mal. No está fallando en inferir el tipo de un delegado. Es que no puede usar un grupo de métodos para inferir uno de los parámetros de tipo. – jason

+0

El delegado requiere T2 como un parámetro genérico, y el compilador no puede inferir el tipo de T2 (de acuerdo con la especificación, como usted señala), se deduce que el compilador no puede inferir el tipo del delegado. Si bien no es la causa raíz, tu explicación es claramente la correcta, no es técnicamente incorrecta. – Dathan

0

Lo siguiente también funciona; No sé por qué:

var outputs = Map(inputs, i => Square(i)); 
+2

Porque a diferencia de los grupos de métodos, las expresiones lambda sí desempeñan un papel en la inferencia de parámetros de tipo. cf. http://msdn.microsoft.com/en-us/library/ms364047(VS.80).aspx#cs3spec_topic4 – jason

1

Al investigar un poco, he encontrado que sus sospechas sobre la otra respuesta son correctas. Esto es lo que dice la especificación de C# 3.0:

7.4.2.1 - Para cada uno de los argumentos del método Ei: Si Ei es una función o método grupo anónimo, un tipo explícito parámetro de inferencia (7.4.2.7) es hecho ... 7.4.2.7 - ... Si E es una función anónima explícitamente tipeada con tipos de parámetro U1 ... Uk y T es un tipo de delegado con tipos de parámetros V1 ... Vk luego para cada Ui una inferencia exacta (§7.4.2.8) se hace desde Ui para el Vi correspondiente.

En otras palabras, los métodos anónimos y grupos de método (que Square es), sólo se puede inferir los tipos de parámetros explícitamente. Creo que la justificación al final de la respuesta que mencionaste lo resume muy bien. Debido a que una inferencia de tipo no siempre se puede hacer implícitamente desde un grupo de métodos, el compilador ni siquiera lo intenta, según la especificación.

1

La razón por la que esto no funciona es porque C# para hacer inferencia de tipo en un método, tiene que saber el tipo de delegado en el otro extremo de la conversión. Pero en este momento, el tipo de delegado objetivo aún no se conoce por completo, solo se conoce T (int), T2 aún no se ha resuelto.

Func<int, int> f = Square; 
//works because we provided the destination type 
//of the conversion from Square to delegate 

Map(inputs, i => Square(i)); 
//works because the lambda follows the actual method call 
//and determines its own return type 
Cuestiones relacionadas