2012-04-11 5 views
8

Estoy intentando crear un delegado acción genéricacontravarianza en expresiones

delegate void ActionPredicate<in T1, in T2>(T1 t1, T2 t2); 

y

public static ActionPredicate<T,string> GetSetterAction<T>(string fieldName) 
    { 

     ParameterExpression targetExpr = Expression.Parameter(typeof(T), "Target"); 
     MemberExpression fieldExpr = Expression.Property(targetExpr, fieldName); 
     ParameterExpression valueExpr = Expression.Parameter(typeof(string), "value"); 

     MethodCallExpression convertExpr = Expression.Call(typeof(Convert), "ChangeType", null, valueExpr, Expression.Constant(fieldExpr.Type)); 

     UnaryExpression valueCast = Expression.Convert(convertExpr, fieldExpr.Type); 
     BinaryExpression assignExpr = Expression.Assign(fieldExpr, valueCast); 
     var result = Expression.Lambda<ActionPredicate<T, string>>(assignExpr, targetExpr, valueExpr); 
     return result.Compile(); 
    } 

y aquí está mi persona que llama

ActionPredicate<busBase, string> act = DelegateGenerator.GetSetterAction<busPerson>("FirstName"); 

y aquí es el objeto de negocio

public abstract class busBase 
{ 

} 
public class busPerson : busBase 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 

    public string GetFullName() 
    { 
     return string.Format("{0} {1}", FirstName, LastName); 
    } 
} 

y aquí está el error lo que consigo durante la compilación

Cannot implicitly convert type 'BusinessObjects.ActionPredicate<BusinessObjects.busPerson,string>' to 'BusinessObjects.ActionPredicate<BusinessObjects.busBase,string>'. An explicit conversion exists (are you missing a cast?)  

Mi GetSetterAction está volviendo ActionPerdicate donde como aquí T es busperson y estoy tratando de almacenarlo en ActionPredicate teniendo en cuenta sobre contravarianza. Pero falla. No sé cómo proceder más. Por favor ayuda..!

+0

¿Has probado la conversión explícita? – McGarnagle

Respuesta

8

contravarianza Genérico hace no le permiten asignar un delegado D<TDerived> a un delegado D<TBase> debido a la razón se muestra a continuación (utilizando Action<T1> aquí):

Action<string> m1 = MyMethod; //some method to call 
Action<object> m2 = m1; //compiler error - but pretend it's not. 
object obj = new object(); 

m2(obj); //runtime error - not type safe 

Como se puede ver, si se nos permitió hacer esta tarea, estaríamos rompiendo la seguridad tipo porque podríamos intentar invocar al delegado m1 pasando e instancia de object y no string. Sin embargo, yendo para otro lado, es decir, copiar una referencia de delegado a un tipo cuyo tipo de parámetro es más derivado que el origen está bien. MSDN has a more complete example of generic co/contra variance.

Por lo tanto, ya sea que se necesite cambiar la declaración de act a ActionPredicate<busPerson, string> act o, más probablemente, que no escribe el método GetSetterAction a volver siempre ActionPredicate<busBase, string>. Si lo hace, también se debe añadir la restricción de tipo

where T1 : busBase 

Para el método, y también tendrá que cambiar cómo se construye su expresión, reemplace las dos primeras líneas de la siguiente manera:

ParameterExpression targetExpr = Expression.Parameter(typeof(busBase), "Target"); 
//generate a strongly-typed downcast to the derived type from busBase and 
//use that as the type on which the property is to be written 
MemberExpression fieldExpr = Expression.Property(
    Expression.Convert(targetExpr, typeof(T1)), fieldName); 

Agregar la restricción genérica es un buen toque para asegurar que este downcast siempre sea válido para cualquier T1.

En una nota ligeramente diferente: ¿qué pasaba con el delegado Action<T1, T2>? Parece hacer exactamente lo mismo que el tuyo? :)

+0

Como sugirió, intenté cambiar GeetSetterAction para devolver Action pero terminé con este error "ParameterExpression del tipo 'BusinessObjects.busPerson' no se puede usar para delegar el parámetro de tipo 'BusinessObjects.busBase'". Estoy recibiendo este error en esta línea. "var result = Expression.Lambda > (assignExpr, targetExpr, valueExpr);" – kans

+0

@kans - ah sí - tendrás que construir el árbol de expresiones ligeramente diferente: downcast de 'busBase' a' T1' al generar 'fieldExpr'. He actualizado mi respuesta. –

+0

Impresionante. Muchas gracias. funciona genial Estoy creando un motor de reglas basado en expresiones. ¿Puedes compartir algunos de tus pensamientos sobre esto? – kans

Cuestiones relacionadas