Contravariance in Expressions
Asked Answered
S

1

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..!

Shavian answered 11/4, 2012 at 7:42 Comment(1)
Did you try the explicit conversion?Zinck
K
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? :)

Kedah answered 11/4, 2012 at 8:0 Comment(6)
As you suggested, i tried to change GeetSetterAction to return Action<busBase,string> 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<Action<busBase, I>>(assignExpr, targetExpr, valueExpr);"Shavian
@Shavian - ah yes - you'll have to build the expression tree slightly differently: downcast from busBase to T1 when generating fieldExpr. Have updated my answer.Kedah
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..Shavian
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!Kedah
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.Shavian
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.Kedah

© 2022 - 2024 — McMap. All rights reserved.