How can I call a reflected Func<T, T> property using Expression Trees

99 views Asked by At

I have a generic class with a lambda property defined as such:

public class Transformation<TProperty> : TransformationBase
{
    public Func<TProperty, TProperty> Transform { get; private set; }
    ...

I'm trying to compile an Action that can call this Transform property (on a property of Foo). I don't know TProperty at compile-time. I've started with this:

    private static Action<Foo> Compile(Transformation transformation)
    {
        var fooParameter = Expression.Parameter(typeof(Foo));
        var changePropertyValue = Expression.Constant(transformation);
        var transformProperty = Expression.Property(changePropertyValue, "Transform");
        var transfromCall = Expression.Call(transformProperty, ?
    }

How can I call/execute the transformProperty?

EDIT: Foo (which is known a compile time) has an untyped property Value which needs to be transformed using the Transform property of the Transformation:

    public class Foo {
         public object Value { get; set; }
    }

So, hand-written as an example where TProperty is string it would be:

    Foo foo = ... // coming from an external source
    Transformation<string> tranformation = ... // coming from an external source
    foo.Value = transformation.Transform((string)foo.Value);

Except that I don't know the exact type of the Transformation as it is defined in an external assembly. So, instead of string it could be int or something else. That's why I want to use Expression Trees to compile an Action for a given transformation, such that I can call:

    Foo foo = ... // coming from an external source
    TransformationBase transformation = ... // coming from an external source
    Action<Foo> transform = Compile(transformation);
    transform(foo); // should transform foo.Value using the Transform property of 'transformation'

Note: I made Transformation inherit from TransformationBase to clarify this discussion.

3

There are 3 answers

1
Shlomo On BEST ANSWER

Your problems relate more to the lack of typing around your problem. Foo.Value is loosely typed, but your transform functions are strongly typed. Expression Trees are also strongly typed. Using them doesn't allow you to magically call code in a loosely typed manner.

The solution is either a lot of reflection, or some easy dynamic:

EDIT: I added CompileUntyped which uses ExpressionTrees.I also added CompileReflection, which uses Reflection without ExpressionTrees. I would recommend the one that uses dynamic. It is by far the easiest to read, hence the easiest to maintain.

class Program
{
    static void Main(string[] args)
    {
        var testTransform = new Transformation<string>
            {
                Transform = s => s.ToUpper()
            };
        var a = Compile(testTransform);
        var foo = new Foo
            {
                Value = "test"
            };

        a(foo);

        //foo.Value is now TEST
    }

    public static Action<Foo> CompileReflection(TransformationBase transformation)
    {
        var f = transformation
            .GetType()
            .GetProperty("Transform")
            .GetGetMethod()
            .Invoke(transformation, null) as Delegate;

        return foo => foo.Value = f.DynamicInvoke(foo.Value);

    }

    public static Action<Foo> Compile(TransformationBase transformation)
    {
        return new Action<Foo>(f =>
            {
                dynamic d = f.Value;
                dynamic t = transformation;
                f.Value = t.Transform(d);
            });
    }

    public static Action<Foo> CompileUntyped(TransformationBase transformation)
    {
        var transformType = transformation.GetType();
        var genericType = transformType.GetGenericArguments().First();

        var fooParam = Expression.Parameter(typeof(Foo), "f");

        var valueGetter = typeof(Foo).GetProperty("Value").GetGetMethod();
        var valueSetter = typeof(Foo).GetProperty("Value").GetSetMethod();
        var transformFuncMember = transformType.GetProperty("Transform").GetGetMethod();

        //Equivalent to f => f.Value = transformation.Transform((T)f.Value)
        //Where T is the generic type parameter of the Transformation, and f is of type Foo
        var expression = Expression.Lambda<Action<Foo>>(
            Expression.Call(
                fooParam,
                valueSetter,
                Expression.Invoke(
                    Expression.Property(
                        Expression.Constant(transformation, transformType), 
                        transformFuncMember
                    ),
                    Expression.Convert(
                        Expression.Property(fooParam, valueGetter),
                        genericType
                    )
                )
            ), fooParam
        );
        return expression.Compile();
    }

}

public class TransformationBase { }

public class Transformation<TProperty> : TransformationBase
{
    public Func<TProperty, TProperty> Transform { get; set; }
}

public class Foo
{
    public object Value { get; set; }
}
1
Konrad Kokosa On

Not sure what are you trying to do BUT if I understand your intentions - I do not see need for compiling Expressions:

private static Action<TProperty> Compile<TProperty>(Transformation<TProperty> transformation)
{
    return new Action<TProperty>(p => transformation.Transform(p));
}
4
Erti-Chris Eelmaa On

See an example, it should give you what you want.

void Main()
{

    var dummyObject = new Dummy { Test = "Hello!" };

    var propertyTransform = Create(dummyObject, "Test");

    propertyTransform(dummyObject);

    Console.WriteLine("Final transformation " + dummyObject.Test);
}

class Dummy {
    public string Test { get; set; }
}

// Define other methods and classes here
public class Transformation<TProperty>
{
    public Func<TProperty, TProperty> Transform { get; set; }
}

public static Action<TObj> Create<TObj>(TObj myObject, string property){
    var prop = myObject
        .GetType()
        .GetProperty(property);

    var val = prop.GetValue(myObject);

    var transformation = Create((dynamic)val);
    var transform = transformation.Transform;

    return obj => {

        var newValue = transform((dynamic)val);

        prop.SetValue(myObject, newValue);
    };
}

public static Transformation<TProperty> Create<TProperty>(TProperty property){

    var transformation = new Transformation<TProperty>();

    // just a dummy hijacking.
    if(typeof(TProperty)==typeof(string)){

        Func<string, string> test = input => "I am changed man!";

        transformation.Transform = (dynamic)test;
    }

    return transformation;
}

Output:

Final transformation I am changed man!