Search code examples
c#.netexpression-trees

Expression.Assign returns Func instead of Action


I'm building a small Expression based property assigner.

The idea is pretty simple, just create an Action that gets property for an object and assigns it to another object property.

So if I process this expression:

public static Action PropertyAssign(object sourceObject, object destObject, 
        PropertyInfo sourceProperty, PropertyInfo destProperty)
{
    Expression source = Expression.PropertyOrField(
    Expression.Constant(sourceObject), sourceProperty.Name);

    Expression dest = Expression.PropertyOrField(
        Expression.Constant(destObject), destProperty.Name);

    Expression assign = Expression.Assign(dest, source);
    return (Action)Expression.Lambda(assign).Compile();
}

and try to call it, I get an exception telling the Expression.Lambda is of type Func (where T is property type)

Since I call assign I would expect to have no remaining value on the stack (so not returning the property itself).

Now If I assign a property using the SetMethod, like :

public static Action PropertyAssign(object sourceObject, object destObject, PropertyInfo sourceProperty, PropertyInfo destProperty)
{
    Expression source = Expression.PropertyOrField(
            Expression.Constant(sourceObject), sourceProperty.Name);
    Expression dest = Expression.PropertyOrField(
            Expression.Constant(destObject), destProperty.Name);
    Expression assign = Expression.Call(
        Expression.Constant(destObject), destProperty.SetMethod, source);
    Action assigner = (Action)Expression.Lambda(assign).Compile();
    return assigner;
}

In that case the function Lambda.Compile() returns an Action with no parameter/return value as expected.

So it's not a blocking issue, but I'm just curious why the assign operator returns a value as well as assigning property (where as Assign should just be calling the setter method as well).

Also is there a way to adapt the first call to return an action instead of a func?


Solution

  • The problem you have is because the assignment operator is in fact a Func, whereas the setter has a void return.

    Normally I would actually avoid using Expression.Lambda exactly due to this problem (and also casting).

    Try this instead.

    public static Action PropertyAssign(object sourceObject, object destObject, 
            PropertyInfo sourceProperty, PropertyInfo destProperty)
    {
        Expression source = Expression.Property(
        Expression.Constant(sourceObject), sourceProperty);
    
        Expression dest = Expression.Property(
            Expression.Constant(destObject), destProperty);
    
        Expression assign = Expression.Assign(dest, source);
        return Expression.Lambda<Action>(assign).Compile();
    }
    

    This does not give any ambiguity in what type of delegate you want the IL to return.

    However this particular piece of code seems completely pointless, as you cannot reuse the IL generated. I would suggest you cache a parameterized version of it.

    public delegate void Assign(TSource sourceObject, TDest destObject);
    
    public static Assign PropertyAssign(TSource sourceObject, TDestdestObject, 
            PropertyInfo sourceProperty, PropertyInfo destProperty)
    {
        var sourceObjectExpression = Expression.Parameter(sourceObject);
        var destPropertyExpression = Expression.Parameter(destProperty);
    
        Expression source = Expression.Property(
                     sourceObjectExpression, sourceProperty);
    
        Expression dest = Expression.Property(
            destPropertyExpression, destProperty);
    
        Expression assign = Expression.Assign(dest, source);
        var lambda = Expression.Lambda<Assign>(
              assign, sourceObjectExpression, destPropertyExpression);
        return lambda.Compile();
    }