returning - use generic type c#
Inferencia de argumento de método genérico de "dos niveles" con delegado (2)
No puede inferir el tipo, porque el tipo no está definido aquí.
Fun2
no es un Func<string, string>
, aunque es algo que puede asignarse a Func<string, string>
.
Así que si usas:
public Test()
{
Func<string, string> del = Fun2;
Fun(del);
}
O:
public Test()
{
Fun((Func<string, string>)Fun2);
}
Entonces, está creando explícitamente un Func<string, string>
de Fun2
, y la inferencia de tipos genérica funciona en consecuencia.
Por el contrario, cuando haces:
public Test()
{
Fun<string>(Fun2);
}
Luego, el conjunto de sobrecargas Fun<string>
contiene solo uno que acepta una Func<string, string>
y el compilador puede inferir que desea usar Fun2
como tal.
Pero le está pidiendo que infiera tanto el tipo genérico basado en el tipo del argumento como el tipo del argumento basado en el tipo genérico. Esa es una pregunta más grande que cualquiera de los tipos de inferencia que puede hacer.
(Vale la pena considerar que en .NET 1.0 no solo los delegados no eran genéricos, por lo que habría tenido que definir la delgate string MyDelegate(string test)
sino que también fue necesario crear el objeto con un constructor Fun(new MyDelegate(Fun2))
La sintaxis ha cambiado para facilitar el uso de los delegados de varias maneras, pero el uso implícito de Fun2
como Func<string, string>
todavía es una construcción de un objeto delegado detrás de las escenas).
Entonces, ¿por qué esto funciona?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
Porque entonces se puede inferir, en orden:
-
T1
esint
. -
Fun2
se asigna aFunc<int, T2>
para algunosT2
. -
Fun2
se puede asignar a unFunc<int, T2>
siT2
es unastring
. Por lo tantoT2
es cadena.
En particular, el tipo de retorno de Func
se puede inferir de una función una vez que tenga los tipos de argumento. Esto es igual de bueno (y vale la pena el esfuerzo por parte del compilador) porque es importante en Linq''s Select
. Eso nos lleva a un caso relacionado, el hecho de que con solo x.Select(i => i.ToString())
no tenemos suficiente información para saber a qué se x.Select(i => i.ToString())
la lambda. Una vez que sepamos si x
es IEnumerable<T>
o IQueryable<T>
, sabemos que tenemos Func<T, ?>
O Expression<Func<T, ?>>
y el resto se puede inferir de allí.
También vale la pena señalar aquí, que deducir el tipo de retorno no está sujeto a una ambigüedad como lo es deducir los otros tipos. Considera si tuvimos tu Fun2
(el que toma una string
y el que toma un int
) en la misma clase. Esta es una sobrecarga válida de C #, pero hace que la deducción del tipo de Func<T, string>
que Fun2
se puede convertir en imposible; ambos son validos
Sin embargo, mientras .NET permite la sobrecarga en el tipo de retorno, C # no lo hace. Por lo tanto, ningún programa de C # válido puede ser ambiguo en el tipo de retorno de un Func<T, TResult>
creado a partir de un método (o lambda) una vez que se determina el tipo de T
Esa relativa facilidad, combinada con la gran utilidad, hace que sea algo que el compilador debe inferir para nosotros.
Considere el siguiente ejemplo:
class Test
{
public void Fun<T>(Func<T, T> f)
{
}
public string Fun2(string test)
{
return "";
}
public Test()
{
Fun<string>(Fun2);
}
}
Esto compila bien.
Me pregunto por qué no puedo eliminar <string>
argumento genérico? Recibo un error que no se puede inferir del uso.
Entiendo que tal inferencia puede ser un desafío para el compilador, pero sin embargo parece posible.
Me gustaría una explicación de este comportamiento.
Editar respondiendo a la respuesta de Jon Hanna:
Entonces, ¿por qué esto funciona?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
En este caso, solo unigo un parámetro con T1 a
, pero T2
parece ser igualmente difícil.
Fun2
que el compilador Fun2
string
de Fun2
, que es demasiado pedir un compilador de C #. Esto se debe a que ve a Fun2
como un grupo de métodos, no un delegado, cuando se hace referencia en Test
.
Si cambia el código para pasar el parámetro que necesita Fun2
y lo invoca desde Fun
, entonces la necesidad desaparece, ya que ahora tiene un parámetro de string
, lo que permite inferir el tipo:
class Test
{
public void Fun<T>(Func<T, T> f, T x)
{
f(x);
}
public string Fun2(string test)
{
return test;
}
public Test()
{
Fun(Fun2, "");
}
}
Para responder a su versión editada de la pregunta, al proporcionar el tipo T1
, ahora ha suministrado el compilador, a través de Fun(0, Fun2);
llamada - información adicional. Ahora sabe que necesita un método, del grupo de métodos Fun2
, que tenga un parámetro T1
, en este caso int
. Esto lo reduce a un método y, por lo tanto, puede inferir cuál usar.