2011-10-18 17 views
12

Esto se explica mejor usando el código. Tengo una clase genérica que tiene un método que devuelve un número entero. Aquí es una versión sencilla para los fines de explicar ...Cómo crear un Expression.Lambda cuando un tipo no se conoce hasta el tiempo de ejecución?

public class Gen<T> 
{ 
    public int DoSomething(T instance) 
    { 
     // Real code does something more interesting! 
     return 1; 
    } 
} 

En tiempo de ejecución que utiliza la reflexión para descubrir el tipo de algo y luego quieren crear una instancia de mi clase Gen para ese tipo específico. Esto es bastante fácil y hecho así ...

Type fieldType = // This is the type I have discovered 
Type genericType = typeof(Gen<>).MakeGenericType(fieldType); 
object genericInstance = Activator.CreateInstance(genericType); 

ahora quiero crear una expresión que va a tomar como parámetro una instancia del tipo genérico y luego llama al método HacerAlgo de ese tipo. Así que quiero la expresión a cabo de forma efectiva este ...

int answer = genericInstance.DoSomething(instance); 

... excepto que no tengo el 'ejemplo' hasta algún momento posterior en tiempo de ejecución y el genericInstance es el tipo generado como se puede ver arriba. Mi intento de crear la Lambda de esto es la siguiente ...

MethodInfo mi = genericType.GetMethod("DoSomething", 
             BindingFlags.Instance | BindingFlags.Public); 

var p1 = Expression.Parameter(genericType, "generic"); 
var p2 = Expression.Parameter(fieldType, "instance"); 

var x = Expression.Lambda<Func<genericType, fieldType, int>> 
      (Expression.Call(p1, mi, p2), 
      new[] { p1, p2 }).Compile(); 

... para que después se puede llamar así con algo como esto ...

int answer = x(genericInstance, instance); 

Por supuesto, no puedo proporcionar a Func los parámetros de la instancia y, por lo tanto, no tengo idea de cómo parametrizar la generación de Lambda. ¿Algunas ideas?

Respuesta

18

creo que usted acaba de utilizar el Expression.Lambda que toma el tipo de delegado como un tipo en lugar de como un genérico, y crear su Func sobre la marcha como si estuviera con Gen<>:

MethodInfo mi = genericType.GetMethod("DoSomething", 
           BindingFlags.Instance | BindingFlags.Public); 

var p1 = Expression.Parameter(genericType, "generic"); 
var p2 = Expression.Parameter(fieldType, "instance"); 
var func = typeof (Func<,,>); 
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int)); 
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2), 
       new[] { p1, p2 }).Compile(); 

Esto devolverá una Delegue en lugar de un Func fuertemente tipado, pero puede, por supuesto, lanzarlo si es necesario (y aparentemente difícil si no sabe a qué está destinando), o invocarlo dinámicamente usando DynamicInvoke en él.

int answer = (int) x.DynamicInvoke(genericInstance, instance); 

EDITAR:

Una buena idea de que en realidad funciona. Desafortunadamente, la razón por la que deseo usar un Lambda compilado fuertemente tipado es el rendimiento. Usar DynamicInvoke es bastante lento en comparación con un Lambda tipado.

Esto parece funcionar sin la necesidad de una invocación dinámica.

var p1 = Expression.Parameter(genericType, "generic"); 
var p2 = Expression.Parameter(fieldType, "instance"); 
var func = typeof(Func<,,>); 
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int)); 
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2), new[] { p1, p2 }); 
var invoke = Expression.Invoke(x, Expression.Constant(genericInstance), Expression.Constant(instance)); 
var answer = Expression.Lambda<Func<int>>(invoke).Compile()(); 

EDIT 2:

Una versión muy simplificada:

Type fieldType = ;// This is the type I have discovered 
Type genericType = typeof(Gen<>).MakeGenericType(fieldType); 
object genericInstance = Activator.CreateInstance(genericType); 
MethodInfo mi = genericType.GetMethod("DoSomething", 
           BindingFlags.Instance | BindingFlags.Public); 
var value = Expression.Constant(instance, fieldType); 
var lambda = Expression.Lambda<Func<int>>(Expression.Call(Expression.Constant(genericInstance), mi, value)); 
var answer = lambda.Compile()(); 
+0

Una buena idea que sí funciona. Desafortunadamente, la razón por la que deseo usar un Lambda compilado fuertemente tipado es el rendimiento. Usar DynamicInvoke es bastante lento en comparación con un Lambda tipado. –

+0

¿Es posible capturar variables en un árbol de Expresión? Ayudaría a capturar la instancia genérica ya que eso nunca cambiará. –

+0

@PhilWright Hm, ya veo. Déjame ver qué más puedo inventar. – vcsjones

1

Esta respuesta sólo se aplica si está utilizando .NET 4.0.

Si haces genericInstancedynamic en lugar de object, a continuación, puede llamar al método DoSomething en él directamente, y el tiempo de ejecución de lenguaje dinámico se hará cargo de todo para usted.

class Type1 { 
    public int DoSomething() { return 1; } 
} 
class Type2 { 
    public int DoSomething() { return 2; } 
} 

static void TestDynamic() { 
    dynamic t1 = Activator.CreateInstance(typeof(Type1)); 
    int answer1 = t1.DoSomething(); // returns 1 

    dynamic t2 = Activator.CreateInstance(typeof(Type2)); 
    int answer2 = t2.DoSomething(); // returns 2 
} 

Si necesita mantener esta estructura de clases (Gen<T>), entonces no veo una manera fácil alrededor del hecho de que usted no sabe el tipo T en tiempo de compilación. Si desea llamar al delegado, debe conocer su tipo completo en tiempo de compilación, o debe pasar los parámetros como objetos.

El uso de dynamic le permite ocultar la complejidad de obtener el MethodInfo, etc., y le ofrece un excelente rendimiento. La única desventaja contra DynamicInvoke que veo es que creo que obtienes la sobrecarga inicial de resolver la llamada dinámica una vez para cada sitio de llamadas. Las vinculaciones se almacenan en caché para que se ejecuten muy rápido desde la segunda vez en adelante si lo llamas en objetos del mismo tipo.

0

Es mejor aceptar object y usar convert a un tipo conocido.

Aquí es un ejemplo, cómo construir el acceso a una propiedad por su nombre en profundidad desconocida:

var model = new { A = new { B = 10L } }; 
string prop = "A.B"; 
var parameter = Expression.Parameter(typeof(object)); 
Func<object, long> expr = (Func<object, long>) Expression.Lambda(prop.Split('.').Aggregate<string, Expression>(Expression.Convert(parameter, model.GetType()), Expression.Property), parameter).Compile(); 
expr(model).Dump(); 

Evita costes adicionales de DynamicInvoke cuando el tipo de delegado no se conoce en tiempo de compilación.

Cuestiones relacionadas