2012-02-23 17 views
11

Sé que lo que estoy haciendo se puede hacer de otra manera, pero tengo curiosidad sobre cómo funcionan las cosas. El siguiente es un código simplificado que no compila, pero se supone que muestra mi objetivo.Pasar una función genérica como parámetro

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
    // Do something with A and B here 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 

Transform es una conversión genérico de cadena a un objeto (piensa deserialización). GeneralizedFunction usa dos especializaciones de transformación: una para el tipo A y otra para el tipo B. Sé que puedo hacer esto de muchas otras maneras (por ejemplo, introduciendo un parámetro para el tipo de objeto), pero estoy buscando explicaciones de si es posible o imposible hacer esto con genéricos/lambdas. Si Transform está especializado antes de pasarlo como parámetro a GeneralizedFunction, entonces es imposible. Entonces la pregunta es por qué esta posibilidad está restringida.

+0

¿Qué es lo quieres hacer exactamente? ya que no desea darle a GeneralizedFunction ningún tipo de información sobre la función Transformar, ¿por qué no acepta un func nuevamente tomando una cadena y devolviendo un objeto (del que todos saben que todo el mundo está *) – Polity

+0

El asunto es que su "Haga algo con Los marcadores de posición A y B ocultan la parte problemática. ¿Serán A y B siempre tipos particulares? Entonces no necesitas genéricos. ¿Son tipos arbitrarios (quizás con restricciones)? Entonces 'GeneralizedFunction' necesita ser genérico en ellos. – AakashM

+0

A y B son tipos concretos, pero Transform es una función genérica. – Max

Respuesta

1

Prueba el siguiente firma:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 

(Tenga en cuenta que GeneralizedFunction tiene que ser genéricos, el compilador automáticamente adivinar el tipo de parámetro al llamar al método).

+0

Lo probé. El problema es que está tratando de referirse tanto a los tipos A y B con T aquí. Era mi intención eliminar de la declaración de la función. – Max

+0

Luego tendrá que reemplazar A y B con T. – Matthias

+0

Necesito realizar operaciones específicas en los objetos dentro de GeneralizedFunction para simplificar. No he escrito ningún detalle en mi código de muestra, pero todo lo que se escribe es necesario. – Max

0
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 
4

Lo que está pidiendo hacer no es posible solo con medicamentos genéricos. El compilador necesita generar dos versiones tipadas de su función Transform: una para devolver el tipo A y otra para el tipo B. El compilador no tiene manera de saber generar esto en tiempo de compilación; solo ejecutando el código sabría que A y B son obligatorios.

Una forma de resolverlo sería pasar en las dos versiones:

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction, Func<string, B> bAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = bAction(aStringB); 
} 

el compilador sabe exactamente lo que necesita para generar en este caso.

+0

Sí, sé que podría aprobar dos instancias, pero esperaba que fuera posible pasar una función GENÉRICA (de ahí el título de mi pregunta) y crear dos especializaciones de esa función genérica dentro de la función Generalizada. – Max

+0

Sé que el código no es lo que quieres. Tu pregunta fue por qué la posibilidad está restringida. Esperemos que comprenda ahora por qué lo que quiere hacer no funcionará con el compilador. –

+0

Si miro el código IL generado para la función Transformar, parece que solo hay una versión del mismo. Incluso cuando lo aplico a dos clases de objetos. Por lo tanto, parece que la especialización de una función genérica se realiza en tiempo de ejecución. ¿No es así? – Max

1

Parece que la respuesta es "no".

Cuando se llama a Transform directamente, tiene que especificar un parámetro de tipo:

int i = Transform<int>(""); 

Así que, hipotéticamente, si se puede pasar una función genérica construida de forma incompleta como desea, que había necesidad de especificar el los parámetros de tipo así:

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction<A>(aStringA); 
    B result2 = aAction<B>(aStringB); 
    // Do something with A and B here 
} 

Por lo tanto, me parece que usted podría hacer hipotéticamente esto, si C# tenía una sintaxis de esa manera.

¿Pero cuál es el caso de uso? Además de transformar las cadenas en el valor predeterminado de un tipo arbitrario, no veo mucho uso para esto. ¿Cómo podría definir una función que proporcionaría un resultado significativo en cualquiera de dos tipos diferentes utilizando la misma serie de enunciados?

EDITAR

Un análisis de por qué no es posible:

Cuando se utiliza una expresión lambda en su código, que se compila en cualquiera de un delegado o un árbol de expresión; en este caso, es un delegado. No puede tener una instancia de tipo genérico "abierto"; en otras palabras, para crear un objeto a partir de un tipo genérico, se deben especificar todos los parámetros de tipo.En otras palabras, no hay forma de tener una instancia de un delegado sin proporcionar argumentos para todos sus parámetros de tipo.

Una de las características útiles del compilador de C es las conversiones de grupos de métodos implícitos, donde el nombre de un método (un "grupo de métodos") se puede convertir implícitamente a un tipo de delegado que representa una de las sobrecargas de ese método. Del mismo modo, el compilador convierte implícitamente una expresión lambda a un tipo de delegado. En ambos casos, el compilador emite código para crear una instancia del tipo de delegado (en este caso, pasarlo a la función). Pero la instancia de ese tipo de delegado aún necesita tener un argumento de tipo para cada uno de sus parámetros de tipo.

Dar la función genérica como función genérica, al parecer, el compilador tendría que ser capaz de pasar el grupo método o la expresión lambda al método sin conversión, por lo que el aAction parámetro de alguna manera tendría un tipo de "grupo de métodos" o "expresión lambda". Entonces, la conversión implícita a un tipo de delegado podría ocurrir en los sitios de llamadas A result1 = aAction<A>(aStringA); y B result2 = aAction<B>(aStringB);. Por supuesto, en este punto, nos adentramos en el universo de contrafactuales e hipotéticos.

La solución que se me ocurrió durante el almuerzo era esto, asumiendo una función Deserialize<T> que toma una cadena que contiene datos serializados y devuelve un objeto de tipo T:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter) 
{ 
    A result1 = Deserialize<A>(stringGetter(aStringA)); 
    B result2 = Deserialize<B>(stringGetter(aStringB)); 
} 

void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b) 
{ 
    GeneralizedFunction(serializedA, serializedB, s => s); 
    GeneralizedFunction(pathToA, pathToB, File.ReadAllText); 
    GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName)); 
} 
+0

Un caso de uso es la deserialización. La cadena es una representación de un objeto y Transform crea una instancia de ese objeto a partir de su representación de cadena – Max

+0

@Max. ¿Pero cuál es el caso de uso para pasar 'Transform' a' GeneralizedFunction' en lugar de simplemente llamarlo directamente? De todos modos, ¿un genérico 'T Transform (string)' no sería un método conveniente para 'object Deserialize (type, string)' como 'T Transform (string s) {return (T) Deserialize (typeof (T) , s); } ' – phoog

+0

Por ejemplo, Transform1 podría convertir una cadena en un objeto, Transform2 podría convertir un archivo que una cadena apunta a un objeto – Max

4

Esta respuesta no explica la razón por qué, solo cómo para evitar la limitación.

En lugar de pasar una función real, se puede pasar un objeto que tiene una función de este tipo:

interface IGenericFunc 
{ 
    TResult Call<TArg,TResult>(TArg arg); 
} 

// ... in some class: 

void Test(IGenericFunc genericFunc) 
{ 
    // for example's sake only: 
    int x = genericFunc.Call<String, int>("string"); 
    object y = genericFunc.Call<double, object>(2.3); 
} 

Para su caso de uso específico, se puede simplificar a:

interface IDeserializerFunc 
{ 
    T Call<T>(string arg); 
} 

// ... in some class: 
void Test(IDeserializerFunc deserializer) 
{ 
    int x = deserializer.Call<int>("3"); 
    double y = deserializer.Call<double>("3.2"); 
} 
Cuestiones relacionadas