JRadness
JRadness

Reputation: 304

Apply Expression to object property in an Expression

I have a class with a property that is another class. I also have an Expression that maps each from other classes. How do I combine these two expressions into one expression without compiling the second Expression?

public class ClassA
{
    public int SomeProperty { get; set; }
}

public class MappedClassA
{
    public int MappedProperty { get; set; }
}

public class ClassB
{
    public ClassA ClassAProperty { get; set; }
    //snip...
}

public class MappedClassB
{
    public MappedClassA  MappedClassAProperty {get; set; }
}

public Expression<Func<ClassA, MappedClassA>> MapAExpression()

{
    return a => new MappedClassA()
    {
        MappedProperty = a.SomeProperty
    };
}

public Expression<Func<ClassB, MappedClassB>> MapBExpression()
{
    return b => new MappedClassB()
    {
        //this should be done with the above expression
        MappedClassAProperty = new MappedClassA()
        {
                MappedProperty = b.ClassAProperty.SomeProperty
        }
    };
}

Upvotes: 1

Views: 146

Answers (1)

HugoRune
HugoRune

Reputation: 13799

This is possible without compiling the expression, but unfortunately not by using the simple lambda syntax to construct the expression.

You will have to create the expression tree "by hand", using expression methods like Expression.New() and Expression.Lambda().

This can get unreadable very quickly.

I tried assembling such an expression from memory below, but I lack practice; this is untested and there are possibly some bugs left.

public Expression<Func<ClassB, MappedClassB>> MapBExpression()
{
    // generate a parameter of type ClassB. 
    // This is the parameter "b" our final lambda expression will accept.
    var classBparam = Expression.Parameter(typeof(ClassB)); 

    // access b.ClassAProperty; this is the property 
    // that we want to pass to the expression returned by MapAExpression() 
    var memberAccess = Expression.MakeMemberAccess(
                          classBparam, 
                          typeof(ClassB).GetProperty("ClassAProperty"));

     // invoke the lambda returned by MapAExpression() 
     //with the parameter b.ClassAProperty 
     var invocation  = Expression.Invoke( MapAExpression(), memberAccess );

     // create a new MappedClassB(), this is the object that will be returned
     // by the expression we are currently creating
     var ctor = Expression.New(typeof(MappedClassB));

     // We want to assign something to the MappedClassB.MappedClassAProperty
     var mappedClassAProperty = 
           typeof(MappedClassB).GetProperty("MappedClassAProperty");

    // specifically, we want to assign the result of our MapAExpression(), 
    // when invoked with the parameter b.ClassAProperty
    var mappedClassAAssignment = 
           Expression.Bind(mappedClassAProperty, invocation);

    // Here we initialize the MappedClassAProperty, 
    // after creating the new MappedClassB.
    // We initialize it with the assignment we just created
    var memberInit = Expression.MemberInit(ctor, mappedClassAAssignment);

    // finally, we construct the lambda 
    //that does all of the above, given a parameter of type ClassB
    return Expression.Lambda<Func<ClassB, MappedClassB>>(memberInit, classBparam);

    // this expression should now be equivalent to:
    // return b => new MappedClassB()
    // {
        // MappedClassAProperty = new MappedClassA()
        // {
            // MappedProperty = b.ClassAProperty.SomeProperty
        // }
    // };

}

Upvotes: 2

Related Questions