8

I'm trying to create a Generic Action Delegate

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

and

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();
    }

and here is my caller

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

and here is the business object

 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);
    }
}

and here is the error what i get during compilation

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

My GetSetterAction is returning ActionPerdicate where as here T is busPerson and i am trying to store it in ActionPredicate keeping in mind about Contravariance. But it fails. i dont know how to proceed further. Please Help..!

kans
  • 225
  • 2
  • 8

1 Answers1

8

Generic contravariance does not allow you to assign a delegate D<TDerived> to a delegate D<TBase> because of the reason demonstrated below (using Action<T1> here):

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

As you can see, if we were allowed to do this assignment, we would then be breaking type-safety because we'd be able to try and invoke the delegate m1 by passing and instance of object and not string. Going the other way, however, i.e. copying a delegate reference to a type whose parameter type is more derived than the source is fine. MSDN has a more complete example of generic co/contra variance.

Therefore you will either need to change the declaration of act to ActionPredicate<busPerson, string> act or, more likely, consider writing the GetSetterAction method to always return ActionPredicate<busBase, string>. If you do that, you should also add the type constraint

where T1 : busBase

To the method, and you'll also need to change how your expression is built, replace the first two lines as follows:

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);

Adding the generic constraint is a nice touch to ensure that this downcast will always be valid for any T1.

On a slightly different note - what was wrong with the Action<T1, T2> delegate? It seems to do exactly the same thing as yours? :)

Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • As you suggested, i tried to change GeetSetterAction to return Action but i ended up with this error "ParameterExpression of type 'BusinessObjects.busPerson' cannot be used for delegate parameter of type 'BusinessObjects.busBase'". I am getting this error at this line. "var result = Expression.Lambda>(assignExpr, targetExpr, valueExpr);" – kans Apr 11 '12 at 13:48
  • @kans - ah yes - you'll have to build the expression tree slightly differently: downcast from `busBase` to `T1` when generating `fieldExpr`. Have updated my answer. – Andras Zoltan Apr 11 '12 at 13:55
  • Awesome. Thanks a lot. it works great. I am creating a Rules Engine based on Expressions. Can you throw some of your thoughts on this.. – kans Apr 12 '12 at 05:23
  • Expressions are immensely powerful and incredibly useful; and the easiest way by far to do dynamic code. Not least because you can *understand* them in code, by walking the tree with a visitor. One word of advice, though, don't expect your colleagues to understand them. The subject is one if those that you have to *want* to learn, and in my experience most people don't! – Andras Zoltan Apr 12 '12 at 07:47
  • Thanks for your comments. Can you throw some piece of code if u have any about the rules xml/c# code etc if somethings comes to your maid or somethings which will help me to have a jumpstart and also about Visitor. – kans Apr 12 '12 at 09:33
  • Hi @kans, this is such a massive topic its difficult to just fire some code at you. I strongly suggest just 'following your nose', start out with an idea of the simplest functionality you can think of, and how the xml will look, and develop it test-first with unit tests. I have an IOC I've written for use in-house that builds code via expressions, and I did that all test-first. It made adding new features a lot easier. – Andras Zoltan Apr 12 '12 at 11:18