Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Contravariance in Expressions

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

like image 580
kans Avatar asked Apr 11 '12 07:04

kans


People also ask

What is the meaning of covariance and contravariance?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

Why is contravariance useful?

This is useful whenever you want to access some common method or property of a collection which can contain items of one or more types derived from a base class. For example, you might have a hierarchy representing different categories of stock for a supermarket.

What is covariance and contravariance in generics in Java?

Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them.

Can you be declared as Contravariant?

A type can be declared contravariant in a generic interface or delegate only if it defines the type of a method's parameters and not of a method's return type. In , ref , and out parameters must be invariant, meaning they are neither covariant nor contravariant.


1 Answers

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? :)

like image 145
Andras Zoltan Avatar answered Oct 16 '22 16:10

Andras Zoltan